--- loncom/lonnet/perl/lonnet.pm	2010/11/11 21:03:30	1.1056.2.11
+++ loncom/lonnet/perl/lonnet.pm	2010/09/26 01:50:33	1.1086
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1056.2.11 2010/11/11 21:03:30 raeburn Exp $
+# $Id: lonnet.pm,v 1.1086 2010/09/26 01:50:33 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -76,7 +76,7 @@ use HTTP::Date;
 use Image::Magick;
 
 use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir
-            $_64bit %env %protocol);
+            $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease);
 
 my (%badServerCache, $memcache, %courselogs, %accesshash, %domainrolehash,
     %userrolehash, $processmarker, $dumpcount, %coursedombuf,
@@ -196,7 +196,7 @@ sub get_server_timezone {
 }
 
 sub get_server_loncaparev {
-    my ($dom,$lonhost) = @_;
+    my ($dom,$lonhost,$ignore_cache,$caller) = @_;
     if (defined($lonhost)) {
         if (!defined(&hostname($lonhost))) {
             undef($lonhost);
@@ -211,15 +211,74 @@ sub get_server_loncaparev {
         }
     }
     if (defined($lonhost)) {
-        my $cachetime = 24*3600;
-        my ($loncaparev,$cached)=&is_cached_new('serverloncaparev',$lonhost);
+        my $cachetime = 12*3600;
+        if (!$ignore_cache) {
+            my ($loncaparev,$cached)=&is_cached_new('serverloncaparev',$lonhost);
+            if (defined($cached)) {
+                return $loncaparev;
+            }
+        }
+        my ($answer,$loncaparev);
+        my @ids=&current_machine_ids();
+        if (grep(/^\Q$lonhost\E$/,@ids)) {
+            $answer = $perlvar{'lonVersion'};
+            if ($answer =~ /^[\'\"]?([\w.\-]+)[\'\"]?$/) {
+                $loncaparev = $1;
+            }
+        } else {
+            $answer = &reply('serverloncaparev',$lonhost);
+            if (($answer eq 'unknown_cmd') || ($answer eq 'con_lost')) {
+                if ($caller eq 'loncron') {
+                    my $ua=new LWP::UserAgent;
+                    $ua->timeout(4);
+                    my $protocol = $protocol{$lonhost};
+                    $protocol = 'http' if ($protocol ne 'https');
+                    my $url = $protocol.'://'.&hostname($lonhost).'/adm/about.html';
+                    my $request=new HTTP::Request('GET',$url);
+                    my $response=$ua->request($request);
+                    unless ($response->is_error()) {
+                        my $content = $response->content;
+                        if ($content =~ /<p>VERSION\:\s*([\w.\-]+)<\/p>/) {
+                            $loncaparev = $1;
+                        }
+                    }
+                } else {
+                    $loncaparev = $loncaparevs{$lonhost};
+                }
+            } elsif ($answer =~ /^[\'\"]?([\w.\-]+)[\'\"]?$/) {
+                $loncaparev = $1;
+            }
+        }
+        return &do_cache_new('serverloncaparev',$lonhost,$loncaparev,$cachetime);
+    }
+}
+
+sub get_server_homeID {
+    my ($hostname,$ignore_cache,$caller) = @_;
+    unless ($ignore_cache) {
+        my ($serverhomeID,$cached)=&is_cached_new('serverhomeID',$hostname);
         if (defined($cached)) {
-            return $loncaparev;
-        } else {
-            my $loncaparev = &reply('serverloncaparev',$lonhost);
-            return &do_cache_new('serverloncaparev',$lonhost,$loncaparev,$cachetime);
+            return $serverhomeID;
         }
     }
+    my $cachetime = 12*3600;
+    my $serverhomeID;
+    if ($caller eq 'loncron') { 
+        my @machine_ids = &machine_ids($hostname);
+        foreach my $id (@machine_ids) {
+            my $response = &reply('serverhomeID',$id);
+            unless (($response eq 'unknown_cmd') || ($response eq 'con_lost')) {
+                $serverhomeID = $response;
+                last;
+            }
+        }
+        if ($serverhomeID eq '') {
+            $serverhomeID = $machine_ids[-1];
+        }
+    } else {
+        $serverhomeID = $serverhomeIDs{$hostname};
+    }
+    return &do_cache_new('serverhomeID',$hostname,$serverhomeID,$cachetime);
 }
 
 # -------------------------------------------------- Non-critical communication
@@ -665,40 +724,26 @@ sub userload {
     return $userloadpercent;
 }
 
-# ------------------------------------------ Fight off request when overloaded
-
-sub overloaderror {
-    my ($r,$checkserver)=@_;
-    unless ($checkserver) { $checkserver=$perlvar{'lonHostID'}; }
-    my $loadavg;
-    if ($checkserver eq $perlvar{'lonHostID'}) {
-       open(my $loadfile,'/proc/loadavg');
-       $loadavg=<$loadfile>;
-       $loadavg =~ s/\s.*//g;
-       $loadavg = 100*$loadavg/$perlvar{'lonLoadLim'};
-       close($loadfile);
-    } else {
-       $loadavg=&reply('load',$checkserver);
-    }
-    my $overload=$loadavg-100;
-    if ($overload>0) {
-	$r->err_headers_out->{'Retry-After'}=$overload;
-        $r->log_error('Overload of '.$overload.' on '.$checkserver);
-        return 413;
-    }    
-    return '';
-}
-
 # ------------------------------ Find server with least workload from spare.tab
 
 sub spareserver {
-    my ($loadpercent,$userloadpercent,$want_server_name) = @_;
+    my ($loadpercent,$userloadpercent,$want_server_name,$udom) = @_;
     my $spare_server;
     if ($userloadpercent !~ /\d/) { $userloadpercent=0; }
     my $lowest_load=($loadpercent > $userloadpercent) ? $loadpercent 
                                                      :  $userloadpercent;
-    
+    my ($uint_dom,$remotesessions);
+    if (($udom ne '') && (&domain($udom) ne '')) {
+        my $uprimary_id = &Apache::lonnet::domain($udom,'primary');
+        $uint_dom = &Apache::lonnet::internet_dom($uprimary_id);
+        my %udomdefaults = &Apache::lonnet::get_domain_defaults($udom);
+        $remotesessions = $udomdefaults{'remotesessions'};
+    }
     foreach my $try_server (@{ $spareid{'primary'} }) {
+        if ($uint_dom) {
+             next unless (&spare_can_host($udom,$uint_dom,$remotesessions,
+                                          $try_server));
+        }
 	($spare_server, $lowest_load) =
 	    &compare_server_load($try_server, $spare_server, $lowest_load);
     }
@@ -707,6 +752,10 @@ sub spareserver {
 
     if (!$found_server) {
 	foreach my $try_server (@{ $spareid{'default'} }) {
+            if ($uint_dom) {
+                next unless (&spare_can_host($udom,$uint_dom,$remotesessions,
+                                             $try_server));
+            }
 	    ($spare_server, $lowest_load) =
 		&compare_server_load($try_server, $spare_server, $lowest_load);
 	}
@@ -719,7 +768,7 @@ sub spareserver {
         }
         if (defined($spare_server)) {
             my $hostname = &hostname($spare_server);
-            if (defined($hostname)) {  
+            if (defined($hostname)) {
 	        $spare_server = $protocol.'://'.$hostname;
             }
         }
@@ -734,7 +783,7 @@ sub compare_server_load {
     my $userloadans = &reply('userload',$try_server);
 
     if ($loadans !~ /\d/ && $userloadans !~ /\d/) {
-	next; #didn't get a number from the server
+	return; #didn't get a number from the server
     }
 
     my $load;
@@ -777,6 +826,27 @@ sub has_user_session {
     return 0;
 }
 
+# --------- determine least loaded server in a user's domain which allows login
+
+sub choose_server {
+    my ($udom) = @_;
+    my %domconfhash = &Apache::loncommon::get_domainconf($udom);
+    my %servers = &get_servers($udom);
+    my $lowest_load = 30000;
+    my ($login_host,$hostname);
+    foreach my $lonhost (keys(%servers)) {
+        my $loginvia = $domconfhash{$udom.'.login.loginvia_'.$lonhost};
+        if ($loginvia eq '') {
+            ($login_host, $lowest_load) =
+            &compare_server_load($lonhost, $login_host, $lowest_load);
+        }
+    }
+    if ($login_host ne '') {
+        $hostname = $servers{$login_host};
+    }
+    return ($login_host,$hostname);
+}
+
 # --------------------------------------------- Try to change a user's password
 
 sub changepass {
@@ -835,7 +905,7 @@ sub queryauthenticate {
 # --------- Try to authenticate user from domain's lib servers (first this one)
 
 sub authenticate {
-    my ($uname,$upass,$udom,$checkdefauth)=@_;
+    my ($uname,$upass,$udom,$checkdefauth,$clientcancheckhost)=@_;
     $upass=&escape($upass);
     $uname= &LONCAPA::clean_username($uname);
     my $uhome=&homeserver($uname,$udom,1);
@@ -858,7 +928,7 @@ sub authenticate {
 	    return 'no_host';
         }
     }
-    my $answer=reply("encrypt:auth:$udom:$uname:$upass:$checkdefauth",$uhome);
+    my $answer=reply("encrypt:auth:$udom:$uname:$upass:$checkdefauth:$clientcancheckhost",$uhome);
     if ($answer eq 'authorized') {
         if ($newhome) {
             &logthis("User $uname at $udom authorized by $uhome, but needs account");
@@ -876,6 +946,84 @@ sub authenticate {
     return 'no_host';
 }
 
+sub can_host_session {
+    my ($udom,$lonhost,$remoterev,$remotesessions,$hostedsessions) = @_;
+    my $canhost = 1;
+    my $host_idn = &Apache::lonnet::internet_dom($lonhost);
+    if (ref($remotesessions) eq 'HASH') {
+        if (ref($remotesessions->{'excludedomain'}) eq 'ARRAY') {
+            if (grep(/^\Q$host_idn\E$/,@{$remotesessions->{'excludedomain'}})) {
+                $canhost = 0;
+            } else {
+                $canhost = 1;
+            }
+        }
+        if (ref($remotesessions->{'includedomain'}) eq 'ARRAY') {
+            if (grep(/^\Q$host_idn\E$/,@{$remotesessions->{'includedomain'}})) {
+                $canhost = 1;
+            } else {
+                $canhost = 0;
+            }
+        }
+        if ($canhost) {
+            if ($remotesessions->{'version'} ne '') {
+                my ($reqmajor,$reqminor) = ($remotesessions->{'version'} =~ /^(\d+)\.(\d+)$/);
+                if ($reqmajor ne '' && $reqminor ne '') {
+                    if ($remoterev =~ /^\'?(\d+)\.(\d+)/) {
+                        my $major = $1;
+                        my $minor = $2;
+                        if (($major < $reqmajor ) ||
+                            (($major == $reqmajor) && ($minor < $reqminor))) {
+                            $canhost = 0;
+                        }
+                    } else {
+                        $canhost = 0;
+                    }
+                }
+            }
+        }
+    }
+    if ($canhost) {
+        if (ref($hostedsessions) eq 'HASH') {
+            if (ref($hostedsessions->{'excludedomain'}) eq 'ARRAY') {
+                if (grep(/^\Q$udom\E$/,@{$hostedsessions->{'excludedomain'}})) {
+                    $canhost = 0;
+                } else {
+                    $canhost = 1;
+                }
+            }
+            if (ref($hostedsessions->{'includedomain'}) eq 'ARRAY') {
+                if (grep(/^\Q$udom\E$/,@{$hostedsessions->{'includedomain'}})) {
+                    $canhost = 1;
+                } else {
+                    $canhost = 0;
+                }
+            }
+        }
+    }
+    return $canhost;
+}
+
+sub spare_can_host {
+    my ($udom,$uint_dom,$remotesessions,$try_server)=@_;
+    my $canhost=1;
+    my @intdoms;
+    my $internet_names = &Apache::lonnet::get_internet_names($try_server);
+    if (ref($internet_names) eq 'ARRAY') {
+        @intdoms = @{$internet_names};
+    }
+    unless (grep(/^\Q$uint_dom\E$/,@intdoms)) {
+        my $serverhomeID = &Apache::lonnet::get_server_homeID($try_server);
+        my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
+        my %defdomdefaults = &Apache::lonnet::get_domain_defaults($serverhomedom);
+        my $remoterev = &Apache::lonnet::get_server_loncaparev(undef,$try_server);
+        $canhost = &can_host_session($udom,$try_server,$remoterev,
+                                     $remotesessions,
+                                     $defdomdefaults{'hostedsessions'});
+    }
+    return $canhost;
+}
+
 # ---------------------- Find the homebase for a user from domain's lib servers
 
 my %homecache;
@@ -1352,7 +1500,7 @@ sub get_domain_defaults {
     my %domconfig =
          &Apache::lonnet::get_dom('configuration',['defaults','quotas',
                                   'requestcourses','inststatus',
-                                  'coursedefaults'],$domain);
+                                  'coursedefaults','usersessions'],$domain);
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
         $domdefaults{'auth_def'} = $domconfig{'defaults'}{'auth_def'};
@@ -1392,6 +1540,14 @@ sub get_domain_defaults {
             $domdefaults{$item} = $domconfig{'coursedefaults'}{$item};
         }
     }
+    if (ref($domconfig{'usersessions'}) eq 'HASH') {
+        if (ref($domconfig{'usersessions'}{'remote'}) eq 'HASH') {
+            $domdefaults{'remotesessions'} = $domconfig{'usersessions'}{'remote'};
+        }
+        if (ref($domconfig{'usersessions'}{'hosted'}) eq 'HASH') {
+            $domdefaults{'hostedsessions'} = $domconfig{'usersessions'}{'hosted'};
+        }
+    }
     &Apache::lonnet::do_cache_new('domdefaults',$domain,\%domdefaults,
                                   $cachetime);
     return %domdefaults;
@@ -2195,7 +2351,7 @@ sub resizeImage {
 
 # --------------- Take an uploaded file and put it into the userfiles directory
 # input: $formname - the contents of the file are in $env{"form.$formname"}
-#                    the desired filename is in $env{"form.$formname.filename"}
+#                    the desired filenam is in $env{"form.$formname.filename"}
 #        $coursedoc - if true up to the current course
 #                     if false
 #        $subdir - directory in userfile to store the file into
@@ -2254,7 +2410,7 @@ sub userfileupload {
     }
     if ($subdir eq 'scantron') {
         $fname = 'scantron_orig_'.$fname;
-    } else {
+    } else {   
 # Create the directory if not present
         $fname="$subdir/$fname";
     }
@@ -3210,7 +3366,7 @@ sub get_domain_roles {
     return %personnel;
 }
 
-# ----------------------------------------------------------- Check out an item
+# ----------------------------------------------------------- Interval timing 
 
 sub get_first_access {
     my ($type,$argsymb)=@_;
@@ -3246,91 +3402,6 @@ sub set_first_access {
     return 'already_set';
 }
 
-sub checkout {
-    my ($symb,$tuname,$tudom,$tcrsid)=@_;
-    my $now=time;
-    my $lonhost=$perlvar{'lonHostID'};
-    my $infostr=&escape(
-                 'CHECKOUTTOKEN&'.
-                 $tuname.'&'.
-                 $tudom.'&'.
-                 $tcrsid.'&'.
-                 $symb.'&'.
-		 $now.'&'.$ENV{'REMOTE_ADDR'});
-    my $token=&reply('tmpput:'.$infostr,$lonhost);
-    if ($token=~/^error\:/) { 
-        &logthis("<font color=\"blue\">WARNING: ".
-                "Checkout tmpput failed ".$tudom.' - '.$tuname.' - '.$symb.
-                 "</font>");
-        return ''; 
-    }
-
-    $token=~s/^(\d+)\_.*\_(\d+)$/$1\*$2\*$lonhost/;
-    $token=~tr/a-z/A-Z/;
-
-    my %infohash=('resource.0.outtoken' => $token,
-                  'resource.0.checkouttime' => $now,
-                  'resource.0.outremote' => $ENV{'REMOTE_ADDR'});
-
-    unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') {
-       return '';
-    } else {
-        &logthis("<font color=\"blue\">WARNING: ".
-                "Checkout cstore failed ".$tudom.' - '.$tuname.' - '.$symb.
-                 "</font>");
-    }    
-
-    if (&log($tudom,$tuname,&homeserver($tuname,$tudom),
-                         &escape('Checkout '.$infostr.' - '.
-                                                 $token)) ne 'ok') {
-	return '';
-    } else {
-        &logthis("<font color=\"blue\">WARNING: ".
-                "Checkout log failed ".$tudom.' - '.$tuname.' - '.$symb.
-                 "</font>");
-    }
-    return $token;
-}
-
-# ------------------------------------------------------------ Check in an item
-
-sub checkin {
-    my $token=shift;
-    my $now=time;
-    my ($ta,$tb,$lonhost)=split(/\*/,$token);
-    $lonhost=~tr/A-Z/a-z/;
-    my $dtoken=$ta.'_'.&hostname($lonhost).'_'.$tb;
-    $dtoken=~s/\W/\_/g;
-    my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)=
-                 split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost)));
-
-    unless (($tuname) && ($tudom)) {
-        &logthis('Check in '.$token.' ('.$dtoken.') failed');
-        return '';
-    }
-    
-    unless (&allowed('mgr',$tcrsid)) {
-        &logthis('Check in '.$token.' ('.$dtoken.') unauthorized: '.
-                 $env{'user.name'}.' - '.$env{'user.domain'});
-        return '';
-    }
-
-    my %infohash=('resource.0.intoken' => $token,
-                  'resource.0.checkintime' => $now,
-                  'resource.0.inremote' => $ENV{'REMOTE_ADDR'});
-
-    unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') {
-       return '';
-    }    
-
-    if (&log($tudom,$tuname,&homeserver($tuname,$tudom),
-                         &escape('Checkin - '.$token)) ne 'ok') {
-	return '';
-    }
-
-    return ($symb,$tuname,$tudom,$tcrsid);    
-}
-
 # --------------------------------------------- Set Expire Date for Spreadsheet
 
 sub expirespread {
@@ -3869,6 +3940,44 @@ sub coursedescription {
     return %returnhash;
 }
 
+sub update_released_required {
+    my ($needsrelease,$cdom,$cnum,$chome,$cid) = @_;
+    if ($cdom eq '' || $cnum eq '' || $chome eq '' || $cid eq '') {
+        $cid = $env{'request.course.id'};
+        $cdom = $env{'course.'.$cid.'.domain'};
+        $cnum = $env{'course.'.$cid.'.num'};
+        $chome = $env{'course.'.$cid.'.home'};
+    }
+    if ($needsrelease) {
+        my %curr_reqd_hash = &userenvironment($cdom,$cnum,'internal.releaserequired');
+        my $needsupdate;
+        if ($curr_reqd_hash{'internal.releaserequired'} eq '') {
+            $needsupdate = 1;
+        } else {
+            my ($currmajor,$currminor) = split(/\./,$curr_reqd_hash{'internal.releaserequired'});
+            my ($needsmajor,$needsminor) = split(/\./,$needsrelease);
+            if (($currmajor < $needsmajor) || ($currmajor == $needsmajor && $currminor < $needsminor)) {
+                $needsupdate = 1;
+            }
+        }
+        if ($needsupdate) {
+            my %needshash = (
+                             'internal.releaserequired' => $needsrelease,
+                            );
+            my $putresult = &put('environment',\%needshash,$cdom,$cnum);
+            if ($putresult eq 'ok') {
+                &appenv({'course.'.$cid.'.internal.releaserequired' => $needsrelease});
+                my %crsinfo = &courseiddump($cdom,'.',1,'.','.',$cnum,undef,undef,'.');
+                if (ref($crsinfo{$cid}) eq 'HASH') {
+                    $crsinfo{$cid}{'releaserequired'} = $needsrelease;
+                    &courseidput($cdom,\%crsinfo,$chome,'notime');
+                }
+            }
+        }
+    }
+    return;
+}
+
 # -------------------------------------------------See if a user is privileged
 
 sub privileged {
@@ -3908,9 +4017,10 @@ sub rolesinit {
     my ($domain,$username,$authhost)=@_;
     my $now=time;
     my %userroles = ('user.login.time' => $now);
-    my $rolesdump=reply("dump:$domain:$username:roles",$authhost);
+    my $extra = &freeze_escape({'skipcheck' => 1});
+    my $rolesdump=reply("dump:$domain:$username:roles:.::$extra",$authhost);
     if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || 
-        ($rolesdump =~ /^error:/)) { 
+        ($rolesdump =~ /^error:/)) {
         return \%userroles;
     }
     my %allroles=();
@@ -4036,7 +4146,7 @@ sub set_userprivs {
     my $adv=0;
     my %grouproles = ();
     if (keys(%{$allgroups}) > 0) {
-        my @groupkeys;
+        my @groupkeys; 
         foreach my $role (keys(%{$allroles})) {
             push(@groupkeys,$role);
         }
@@ -4059,7 +4169,7 @@ sub set_userprivs {
                         foreach my $group (keys(%{$$allgroups{$area}})) {
                             my $spec = $trole.'.'.$extendedarea;
                             $grouproles{$spec.'.'.$area.'/'.$group} = 
-                                                  $$allgroups{$area}{$group};
+                                                $$allgroups{$area}{$group};
                         }
                     }
                 }
@@ -4112,7 +4222,7 @@ sub role_status {
                             my %userroles = (
                                 'user.role.'.$$role.'.'.$$where => $$tstart.'.'.$$tend
                             );
-                            @rolecodes = ('cm');
+                            @rolecodes = ('cm'); 
                             my $spec=$$role.'.'.$$where;
                             my ($tdummy,$tdomain,$trest)=split(/\//,$$where);
                             if ($$role =~ /^cr\//) {
@@ -4129,7 +4239,7 @@ sub role_status {
                                 my %course_roles = &get_my_roles($env{'user.name'},$env{'user.domain'},'userroles',['active'],['cc','co','in','ta','ep','ad','st','cr'],[$tdomain],1);
                                 if (keys(%course_roles) > 0) {
                                     my ($tnum) = ($trest =~ /^($match_courseid)/);
-                                    if ($tdomain ne '' && $tnum ne '') {
+                                    if ($tdomain ne '' && $tnum ne '') { 
                                         foreach my $key (keys(%course_roles)) {
                                             if ($key =~ /^\Q$tnum\E:\Q$tdomain\E:([^:]+):?([^:]*)/) {
                                                 my $crsrole = $1;
@@ -4177,22 +4287,22 @@ sub role_status {
 }
 
 sub check_adhoc_privs {
-    my ($cdom,$cnum,$then,$refresh,$now,$checkrole,$caller) = @_;
+    my ($cdom,$cnum,$then,$refresh,$now,$checkrole) = @_;
     my $cckey = 'user.role.'.$checkrole.'./'.$cdom.'/'.$cnum;
     if ($env{$cckey}) {
         my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend);
         &role_status($cckey,$then,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend);
         unless (($tstatus eq 'is') || ($tstatus eq 'will_not')) {
-            &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller);
+            &set_adhoc_privileges($cdom,$cnum,$checkrole);
         }
     } else {
-        &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller);
+        &set_adhoc_privileges($cdom,$cnum,$checkrole);
     }
 }
 
 sub set_adhoc_privileges {
 # role can be cc or ca
-    my ($dcdom,$pickedcourse,$role,$caller) = @_;
+    my ($dcdom,$pickedcourse,$role) = @_;
     my $area = '/'.$dcdom.'/'.$pickedcourse;
     my $spec = $role.'.'.$area;
     my %userroles = &set_arearole($role,$area,'','',$env{'user.domain'},
@@ -4202,16 +4312,14 @@ sub set_adhoc_privileges {
     my ($author,$adv)= &set_userprivs(\%userroles,\%ccrole);
     &appenv(\%userroles,[$role,'cm']);
     &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role);
-    unless ($caller eq 'constructaccess' && $env{'request.course.id'}) {
-        &appenv( {'request.role'        => $spec,
-                  'request.role.domain' => $dcdom,
-                  'request.course.sec'  => ''
-                 }
-               );
-        my $tadv=0;
-        if (&allowed('adv') eq 'F') { $tadv=1; }
-        &appenv({'request.role.adv'    => $tadv});
-    }
+    &appenv( {'request.role'        => $spec,
+              'request.role.domain' => $dcdom,
+              'request.course.sec'  => ''
+             }
+           );
+    my $tadv=0;
+    if (&allowed('adv') eq 'F') { $tadv=1; }
+    &appenv({'request.role.adv'    => $tadv});
 }
 
 # --------------------------------------------------------------- get interface
@@ -4814,7 +4922,7 @@ sub is_portfolio_file {
 }
 
 sub usertools_access {
-    my ($uname,$udom,$tool,$action,$context) = @_;
+    my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref)=@_;
     my ($access,%tools);
     if ($context eq '') {
         $context = 'tools';
@@ -4856,9 +4964,14 @@ sub usertools_access {
         $toolstatus = $env{'environment.'.$context.'.'.$tool};
         $inststatus = $env{'environment.inststatus'};
     } else {
-        my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus');
-        $toolstatus = $userenv{$context.'.'.$tool};
-        $inststatus = $userenv{'inststatus'};
+        if (ref($userenvref) eq 'HASH') {
+            $toolstatus = $userenvref->{$context.'.'.$tool};
+            $inststatus = $userenvref->{'inststatus'};
+        } else {
+            my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus');
+            $toolstatus = $userenv{$context.'.'.$tool};
+            $inststatus = $userenv{'inststatus'};
+        }
     }
 
     if ($toolstatus ne '') {
@@ -4870,8 +4983,17 @@ sub usertools_access {
         return $access;
     }
 
-    my $is_adv = &is_advanced_user($udom,$uname);
-    my %domdef = &get_domain_defaults($udom);
+    my ($is_adv,%domdef);
+    if (ref($is_advref) eq 'HASH') {
+        $is_adv = $is_advref->{'is_adv'};
+    } else {
+        $is_adv = &is_advanced_user($udom,$uname);
+    }
+    if (ref($domdefref) eq 'HASH') {
+        %domdef = %{$domdefref};
+    } else {
+        %domdef = &get_domain_defaults($udom);
+    }
     if (ref($domdef{$tool}) eq 'HASH') {
         if ($is_adv) {
             if ($domdef{$tool}{'_LC_adv'} ne '') {
@@ -6662,8 +6784,9 @@ sub modifyuser {
 #
 # If name, email and/or uid are blank (e.g., because an uploaded file
 # of users did not contain them), do not overwrite existing values
-# unless field is in $candelete array ref.
+# unless field is in $candelete array ref.  
 #
+
     my @fields = ('firstname','middlename','lastname','generation',
                   'permanentemail','id');
     my %newvalues;
@@ -6676,7 +6799,7 @@ sub modifyuser {
                     $names{$field} = $middle;
                 } elsif ($field eq 'lastname') {
                     $names{$field} = $last;
-                } elsif ($field eq 'generation') {
+                } elsif ($field eq 'generation') { 
                     $names{$field} = $gene;
                 } elsif ($field eq 'permanentemail') {
                     $names{$field} = $email;
@@ -6686,7 +6809,6 @@ sub modifyuser {
             }
         }
     }
-
     if ($first)  { $names{'firstname'}  = $first; }
     if (defined($middle)) { $names{'middlename'} = $middle; }
     if ($last)   { $names{'lastname'}   = $last; }
@@ -6713,7 +6835,7 @@ sub modifyuser {
     }
     my $logmsg = $udom.', '.$uname.', '.$uid.', '.
                  $umode.', '.$first.', '.$middle.', '.
-	         $last.', '.$gene.', '.$email.', '.$inststatus;
+                 $last.', '.$gene.', '.$email.', '.$inststatus;
     if ($env{'user.name'} ne '' && $env{'user.domain'}) {
         $logmsg .= ' by '.$env{'user.name'}.' at '.$env{'user.domain'};
     } else {
@@ -6736,12 +6858,9 @@ sub modifyuser {
         return 'ok';
     }
     my $reply = &put('environment', \%names, $udom,$uname);
-    if ($reply ne 'ok') {
+    if ($reply ne 'ok') { 
         return 'error: '.$reply;
     }
-    if ($names{'permanentemail'} ne $oldnames{'permanentemail'}) {
-        &Apache::lonnet::devalidate_cache_new('emailscache',$uname.':'.$udom);
-    }
     my $sqlresult = &update_allusers_table($uname,$udom,\%names);
     &devalidate_cache_new('namescache',$uname.':'.$udom);
     $logmsg = 'Success modifying user '.$logmsg;
@@ -7118,7 +7237,7 @@ sub is_locked {
     my ($file_name, $domain, $user) = @_;
     my @check;
     my $is_locked;
-    push(@check,$file_name);
+    push @check, $file_name;
     my %locked = &get('file_permissions',\@check,
 		      $env{'user.domain'},$env{'user.name'});
     my ($tmp)=keys(%locked);
@@ -7135,7 +7254,6 @@ sub is_locked {
     } else {
         $is_locked = 'false';
     }
-    return $is_locked;
 }
 
 sub declutter_portfile {
@@ -8278,6 +8396,7 @@ sub add_prefix_and_part {
 # ---------------------------------------------------------------- Get metadata
 
 my %metaentry;
+my %importedpartids;
 sub metadata {
     my ($uri,$what,$liburi,$prefix,$depthcount)=@_;
     $uri=&declutter($uri);
@@ -8285,7 +8404,7 @@ sub metadata {
     if (($uri eq '') || 
 	(($uri =~ m|^/*adm/|) && 
 	     ($uri !~ m|^adm/includes|) && ($uri !~ m|/bulletinboard$|)) ||
-        ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ /^\*uploaded\/.+\.sequence$/) ) {
+        ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) ) {
 	return undef;
     }
     if (($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) 
@@ -8304,6 +8423,10 @@ sub metadata {
 	if (defined($cached)) { return $result->{':'.$what}; }
     }
     {
+# Imported parts would go here
+        my %importedids=();
+        my @origfileimportpartids=();
+        my $importedparts=0;
 #
 # Is this a recursive call for a library?
 #
@@ -8387,27 +8510,55 @@ sub metadata {
 # This is not a package - some other kind of start tag
 #
 		    my $entry=$token->[1];
-		    my $unikey;
-		    if ($entry eq 'import') {
-			$unikey='';
-		    } else {
-			$unikey=$entry;
-		    }
-		    $unikey.=&add_prefix_and_part($prefix,$token->[2]->{'part'});
-
-		    if (defined($token->[2]->{'id'})) { 
-			$unikey.='_'.$token->[2]->{'id'}; 
-		    }
+		    my $unikey='';
 
 		    if ($entry eq 'import') {
 #
 # Importing a library here
 #
+                        my $location=$parser->get_text('/import');
+                        my $dir=$filename;
+                        $dir=~s|[^/]*$||;
+                        $location=&filelocation($dir,$location);
+                       
+                        my $importmode=$token->[2]->{'importmode'};
+                        if ($importmode eq 'problem') {
+# Import as problem/response
+                           $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'});
+                        } elsif ($importmode eq 'part') {
+# Import as part(s)
+                           $importedparts=1;
+# We need to get the original file and the imported file to get the part order correct
+# Good news: we do not need to worry about nested libraries, since parts cannot be nested
+# Load and inspect original file
+                           if ($#origfileimportpartids<0) {
+                              undef(%importedpartids);
+                              my $origfilelocation=$perlvar{'lonDocRoot'}.&clutter($uri);
+                              my $origfile=&getfile($origfilelocation);
+                              @origfileimportpartids=($origfile=~/<(part|import)[^>]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs);
+                           }
+
+# Load and inspect imported file
+                           my $impfile=&getfile($location);
+                           my @impfilepartids=($impfile=~/<part[^>]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs);
+                           if ($#impfilepartids>=0) {
+# This problem had parts
+                               $importedpartids{$token->[2]->{'id'}}=join(',',@impfilepartids);
+                           } else {
+# Importing by turning a single problem into a problem part
+# It gets the import-tags ID as part-ID
+                               $unikey=&add_prefix_and_part($prefix,$token->[2]->{'id'});
+                               $importedpartids{$token->[2]->{'id'}}=$token->[2]->{'id'};
+                           }
+                        } else {
+# Normal import
+                           $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'});
+                           if (defined($token->[2]->{'id'})) {
+                              $unikey.='_'.$token->[2]->{'id'};
+                           }
+                        }
+
 			if ($depthcount<20) {
-			    my $location=$parser->get_text('/import');
-			    my $dir=$filename;
-			    $dir=~s|[^/]*$||;
-			    $location=&filelocation($dir,$location);
 			    my $metadata = 
 				&metadata($uri,'keys', $location,$unikey,
 					  $depthcount+1);
@@ -8415,9 +8566,16 @@ sub metadata {
 				$metaentry{':'.$meta}=$metaentry{':'.$meta};
 				$metathesekeys{$meta}=1;
 			    }
-			}
-		    } else { 
 			
+                        }
+		    } else {
+#
+# Not importing, some other kind of non-package, non-library start tag
+# 
+                        $unikey=$entry.&add_prefix_and_part($prefix,$token->[2]->{'part'});
+                        if (defined($token->[2]->{'id'})) {
+                            $unikey.='_'.$token->[2]->{'id'};
+                        }
 			if (defined($token->[2]->{'name'})) { 
 			    $unikey.='_'.$token->[2]->{'name'}; 
 			}
@@ -8491,6 +8649,22 @@ sub metadata {
 	    grep { ! $seen{$_} ++ } (split(',',$metaentry{':packages'}));
 	$metaentry{':packages'} = join(',',@uniq_packages);
 
+        if ($importedparts) {
+# We had imported parts and need to rebuild partorder
+           $metaentry{':partorder'}='';
+           $metathesekeys{'partorder'}=1;
+           for (my $index=0;$index<$#origfileimportpartids;$index+=2) {
+               if ($origfileimportpartids[$index] eq 'part') {
+# original part, part of the problem
+                  $metaentry{':partorder'}.=','.$origfileimportpartids[$index+1];
+               } else {
+# we have imported parts at this position
+                  $metaentry{':partorder'}.=','.$importedpartids{$origfileimportpartids[$index+1]};
+               }
+           }
+           $metaentry{':partorder'}=~s/^\,//;
+        }
+
 	$metaentry{':keys'} = join(',',keys(%metathesekeys));
 	&metadata_generate_part0(\%metathesekeys,\%metaentry,$uri);
 	$metaentry{':allpossiblekeys'}=join(',',keys %metathesekeys);
@@ -9767,6 +9941,7 @@ sub get_dns {
     my %libserv;
     my $loaded;
     my %name_to_host;
+    my %internetdom;
 
     sub parse_hosts_tab {
 	my ($file) = @_;
@@ -9774,7 +9949,7 @@ sub get_dns {
 	    next if ($configline =~ /^(\#|\s*$ )/x);
 	    next if ($configline =~ /^\^/);
 	    chomp($configline);
-	    my ($id,$domain,$role,$name,$protocol)=split(/:/,$configline);
+	    my ($id,$domain,$role,$name,$protocol,$intdom)=split(/:/,$configline);
 	    $name=~s/\s//g;
 	    if ($id && $domain && $role && $name) {
 		$hostname{$id}=$name;
@@ -9790,6 +9965,9 @@ sub get_dns {
                 } else {
                     $protocol{$id} = 'http';
                 }
+                if (defined($intdom)) {
+                    $internetdom{$id} = $intdom;
+                }
 	    }
 	}
     }
@@ -9851,6 +10029,12 @@ sub get_dns {
 	return %libserv;
     }
 
+    sub unique_library {
+	#2x reverse removes all hostnames that appear more than once
+        my %unique = reverse &all_library();
+        return reverse %unique;
+    }
+
     sub get_servers {
 	&load_hosts_tab() if (!$loaded);
 
@@ -9874,6 +10058,11 @@ sub get_dns {
 	return %result;
     }
 
+    sub get_unique_servers {
+        my %unique = reverse &get_servers(@_);
+	return reverse %unique;
+    }
+
     sub host_domain {
 	&load_hosts_tab() if (!$loaded);
 
@@ -9888,6 +10077,13 @@ sub get_dns {
 	my @uniq = grep(!$seen{$_}++, values(%hostdom));
 	return @uniq;
     }
+
+    sub internet_dom {
+        &load_hosts_tab() if (!$loaded);
+
+        my ($lonid) = @_;
+        return $internetdom{$lonid};
+    }
 }
 
 { 
@@ -10005,6 +10201,40 @@ sub get_dns {
         return undef;
     }
 
+    sub get_internet_names {
+        my ($lonid) = @_;
+        return if ($lonid eq '');
+        my ($idnref,$cached)=
+            &Apache::lonnet::is_cached_new('internetnames',$lonid);
+        if ($cached) {
+            return $idnref;
+        }
+        my $ip = &get_host_ip($lonid);
+        my @hosts = &get_hosts_from_ip($ip);
+        my %iphost = &get_iphost();
+        my (@idns,%seen);
+        foreach my $id (@hosts) {
+            my $dom = &host_domain($id);
+            my $prim_id = &domain($dom,'primary');
+            my $prim_ip = &get_host_ip($prim_id);
+            next if ($seen{$prim_ip});
+            if (ref($iphost{$prim_ip}) eq 'ARRAY') {
+                foreach my $id (@{$iphost{$prim_ip}}) {
+                    my $intdom = &internet_dom($id);
+                    unless (grep(/^\Q$intdom\E$/,@idns)) {
+                        push(@idns,$intdom);
+                    }
+                }
+            }
+            $seen{$prim_ip} = 1;
+        }
+        return &Apache::lonnet::do_cache_new('internetnames',$lonid,\@idns,12*60*60);
+    }
+
+}
+
+sub all_loncaparevs {
+    return qw(1.1 1.2 1.3 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10);
 }
 
 BEGIN {
@@ -10082,6 +10312,53 @@ BEGIN {
     close($config);
 }
 
+# ---------------------------------------------------------- Read loncaparev table
+{
+    if (-e "$perlvar{'lonTabDir'}/loncaparevs.tab") {
+        if (open(my $config,"<$perlvar{'lonTabDir'}/loncaparevs.tab")) {
+            while (my $configline=<$config>) {
+                chomp($configline);
+                my ($hostid,$loncaparev)=split(/:/,$configline);
+                $loncaparevs{$hostid}=$loncaparev;
+            }
+            close($config);
+        }
+    }
+}
+
+# ---------------------------------------------------------- Read serverhostID table
+{
+    if (-e "$perlvar{'lonTabDir'}/serverhomeIDs.tab") {
+        if (open(my $config,"<$perlvar{'lonTabDir'}/serverhomeIDs.tab")) {
+            while (my $configline=<$config>) {
+                chomp($configline);
+                my ($name,$id)=split(/:/,$configline);
+                $serverhomeIDs{$name}=$id;
+            }
+            close($config);
+        }
+    }
+}
+
+{
+    my $file = $Apache::lonnet::perlvar{'lonTabDir'}.'/releaseslist.xml';
+    if (-e $file) {
+        my $parser = HTML::LCParser->new($file);
+        while (my $token = $parser->get_token()) {
+            if ($token->[0] eq 'S') {
+                my $item = $token->[1];
+                my $name = $token->[2]{'name'};
+                my $value = $token->[2]{'value'};
+                if ($item ne '' && $name ne '' && $value ne '') {
+                    my $release = $parser->get_text();
+                    $release =~ s/(^\s*|\s*$ )//gx;
+                    $needsrelease{$item.':'.$name.':'.$value} = $release;
+                }
+            }
+        }
+    }
+}
+
 # ------------- set up temporary directory
 {
     $tmpdir = $perlvar{'lonDaemons'}.'/tmp/';
@@ -10312,9 +10589,14 @@ authentication scheme
 
 =item *
 X<authenticate()>
-B<authenticate($uname,$upass,$udom)>: try to
+B<authenticate($uname,$upass,$udom,$checkdefauth,$clientcancheckhost)>: try to
 authenticate user from domain's lib servers (first use the current
 one). C<$upass> should be the users password.
+$checkdefauth is optional (value is 1 if a check should be made to
+   authenticate user using default authentication method, and allow
+   account creation if username does not have account in the domain).
+$clientcancheckhost is optional (value is 1 if checking whether the
+   server can host will occur on the client side in lonauth.pm).   
 
 =item *
 X<homeserver()>
@@ -10442,7 +10724,7 @@ modifyuser($udom,$uname,$uid,$umode,$upa
 will update user information (firstname,middlename,lastname,generation,
 permanentemail), and if forceid is true, student/employee ID also.
 A user's institutional affiliation(s) can also be updated.
-User information fields will not be overwritten with empty entries
+User information fields will not be overwritten with empty entries 
 unless the field is included in the $candelete array reference.
 This array is included when a single user is modified via "Manage Users",
 or when Autoupdate.pl is run by cron in a domain.