--- loncom/lonnet/perl/lonnet.pm	2020/11/24 16:38:00	1.1172.2.132
+++ loncom/lonnet/perl/lonnet.pm	2021/11/09 21:04:30	1.1172.2.140.2.3
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1172.2.132 2020/11/24 16:38:00 raeburn Exp $
+# $Id: lonnet.pm,v 1.1172.2.140.2.3 2021/11/09 21:04:30 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -125,12 +125,13 @@ our @EXPORT = qw(%env);
 	$logid ++;
         my $now = time();
 	my $id=$now.'00000'.$$.'00000'.$logid;
+        my $ip = &get_requestor_ip();  
         my $logentry = {
                          $id => {
                                   'exe_uname' => $env{'user.name'},
                                   'exe_udom'  => $env{'user.domain'},
                                   'exe_time'  => $now,
-                                  'exe_ip'    => $ENV{'REMOTE_ADDR'},
+                                  'exe_ip'    => $ip,
                                   'delflag'   => $delflag,
                                   'logentry'  => $storehash,
                                   'uname'     => $uname,
@@ -1375,6 +1376,15 @@ sub spare_can_host {
             $canhost = 0;
         }
     }
+    if ($canhost) {
+        if (ref($defdomdefaults{'offloadoth'}) eq 'HASH') {
+            if ($defdomdefaults{'offloadoth'}{$try_server}) {
+                unless (&shared_institution($udom,$try_server)) {
+                    $canhost = 0;
+                }
+            }
+        }
+    }
     if (($canhost) && ($uint_dom)) {
         my @intdoms;
         my $internet_names = &get_internet_names($try_server);
@@ -1888,7 +1898,13 @@ sub get_dom {
         }
     }
     if ($udom && $uhome && ($uhome ne 'no_host')) {
-        my $rep=&reply("getdom:$udom:$namespace:$items",$uhome);
+        my $rep;
+        if (grep { $_ eq $uhome } &current_machine_ids()) {
+            # domain information is hosted on this machine
+            $rep = &LONCAPA::Lond::get_dom("getdom:$udom:$namespace:$items");
+        } else {        
+            $rep=&reply("getdom:$udom:$namespace:$items",$uhome);
+        }
         my %returnhash;
         if ($rep eq '' || $rep =~ /^error: 2 /) {
             return %returnhash;
@@ -2415,6 +2431,9 @@ sub get_domain_defaults {
         if (ref($domconfig{'usersessions'}{'offloadnow'}) eq 'HASH') {
             $domdefaults{'offloadnow'} = $domconfig{'usersessions'}{'offloadnow'};
         }
+        if (ref($domconfig{'usersessions'}{'offloadoth'}) eq 'HASH') {
+            $domdefaults{'offloadoth'} = $domconfig{'usersessions'}{'offloadoth'};
+        }
     }
     if (ref($domconfig{'selfenrollment'}) eq 'HASH') {
         if (ref($domconfig{'selfenrollment'}{'admin'}) eq 'HASH') {
@@ -3717,6 +3736,10 @@ sub clean_filename {
 # Replace all .\d. sequences with _\d. so they no longer look like version
 # numbers
     $fname=~s/\.(\d+)(?=\.)/_$1/g;
+# Replace three or more adjacent underscores with one for consistency
+# with loncfile::filename_check() so complete url can be extracted by
+# lonnet::decode_symb()
+    $fname=~s/_{3,}/_/g;
     return $fname;
 }
 
@@ -5447,9 +5470,10 @@ my %cachedtimes=();
 my $cachedtime='';
 
 sub load_all_first_access {
-    my ($uname,$udom)=@_;
+    my ($uname,$udom,$ignorecache)=@_;
     if (($cachedkey eq $uname.':'.$udom) &&
-        (abs($cachedtime-time)<5) && (!$env{'form.markaccess'})) {
+        (abs($cachedtime-time)<5) && (!$env{'form.markaccess'}) &&
+        (!$ignorecache)) {
         return;
     }
     $cachedtime=time;
@@ -5458,7 +5482,7 @@ sub load_all_first_access {
 }
 
 sub get_first_access {
-    my ($type,$argsymb,$argmap)=@_;
+    my ($type,$argsymb,$argmap,$ignorecache)=@_;
     my ($symb,$courseid,$udom,$uname)=&whichuser();
     if ($argsymb) { $symb=$argsymb; }
     my ($map,$id,$res)=&decode_symb($symb);
@@ -5470,7 +5494,7 @@ sub get_first_access {
     } else {
 	$res=$symb;
     }
-    &load_all_first_access($uname,$udom);
+    &load_all_first_access($uname,$udom,$ignorecache);
     return $cachedtimes{"$courseid\0$res"};
 }
 
@@ -5522,13 +5546,14 @@ sub checkout {
     my ($symb,$tuname,$tudom,$tcrsid)=@_;
     my $now=time;
     my $lonhost=$perlvar{'lonHostID'};
+    my $ip = &get_requestor_ip();
     my $infostr=&escape(
                  'CHECKOUTTOKEN&'.
                  $tuname.'&'.
                  $tudom.'&'.
                  $tcrsid.'&'.
                  $symb.'&'.
-                 $now.'&'.$ENV{'REMOTE_ADDR'});
+                 $now.'&'.$ip);
     my $token=&reply('tmpput:'.$infostr,$lonhost);
     if ($token=~/^error\:/) {
         &logthis("<font color=\"blue\">WARNING: ".
@@ -5542,7 +5567,7 @@ sub checkout {
 
     my %infohash=('resource.0.outtoken' => $token,
                   'resource.0.checkouttime' => $now,
-                  'resource.0.outremote' => $ENV{'REMOTE_ADDR'});
+                  'resource.0.outremote' => $ip);
 
     unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') {
        return '';
@@ -5573,6 +5598,7 @@ sub checkin {
     $lonhost=~tr/A-Z/a-z/;
     my $dtoken=$ta.'_'.&hostname($lonhost).'_'.$tb;
     $dtoken=~s/\W/\_/g;
+    my $ip = &get_requestor_ip();
     my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)=
                  split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost)));
 
@@ -5589,7 +5615,7 @@ sub checkin {
 
     my %infohash=('resource.0.intoken' => $token,
                   'resource.0.checkintime' => $now,
-                  'resource.0.inremote' => $ENV{'REMOTE_ADDR'});
+                  'resource.0.inremote' => $ip);
 
     unless (&cstore(\%infohash,$symb,$tcrsid,$tudom,$tuname) eq 'ok') {
        return '';
@@ -5857,7 +5883,7 @@ sub tmpreset {
   if (!$domain) { $domain=$env{'user.domain'}; }
   if (!$stuname) { $stuname=$env{'user.name'}; }
   if ($domain eq 'public' && $stuname eq 'public') {
-      $stuname=$ENV{'REMOTE_ADDR'};
+      $stuname=&get_requestor_ip();
   }
   my $path=LONCAPA::tempdir();
   my %hash;
@@ -5894,7 +5920,7 @@ sub tmpstore {
   if (!$domain) { $domain=$env{'user.domain'}; }
   if (!$stuname) { $stuname=$env{'user.name'}; }
   if ($domain eq 'public' && $stuname eq 'public') {
-      $stuname=$ENV{'REMOTE_ADDR'};
+      $stuname=&get_requestor_ip();
   }
   my $now=time;
   my %hash;
@@ -5938,7 +5964,7 @@ sub tmprestore {
   if (!$domain) { $domain=$env{'user.domain'}; }
   if (!$stuname) { $stuname=$env{'user.name'}; }
   if ($domain eq 'public' && $stuname eq 'public') {
-      $stuname=$ENV{'REMOTE_ADDR'};
+      $stuname=&get_requestor_ip();
   }
   my %returnhash;
   $namespace=~s/\//\_/g;
@@ -5994,7 +6020,7 @@ sub store {
     }
     if (!$home) { $home=$env{'user.home'}; }
 
-    $$storehash{'ip'}=$ENV{'REMOTE_ADDR'};
+    $$storehash{'ip'}=&get_requestor_ip();
     $$storehash{'host'}=$perlvar{'lonHostID'};
 
     my $namevalue='';
@@ -6030,7 +6056,7 @@ sub cstore {
     }
     if (!$home) { $home=$env{'user.home'}; }
 
-    $$storehash{'ip'}=$ENV{'REMOTE_ADDR'};
+    $$storehash{'ip'}=&get_requestor_ip();
     $$storehash{'host'}=$perlvar{'lonHostID'};
 
     my $namevalue='';
@@ -7022,7 +7048,8 @@ sub putstore {
        foreach my $key (keys(%{$storehash})) {
            $namevalue.=&escape($key).'='.&freeze_escape($storehash->{$key}).'&';
        }
-       $namevalue .= 'ip='.&escape($ENV{'REMOTE_ADDR'}).
+       my $ip = &get_requestor_ip();
+       $namevalue .= 'ip='.&escape($ip).
                      '&host='.&escape($perlvar{'lonHostID'}).
                      '&version='.$esc_v.
                      '&by='.&escape($env{'user.name'}.':'.$env{'user.domain'});
@@ -7820,7 +7847,7 @@ sub allowed {
 
     if (defined($env{'allowed.'.$priv})) { return $env{'allowed.'.$priv}; }
 # Free bre access to adm and meta resources
-    if (((($uri=~/^adm\//) && ($uri !~ m{/(?:smppg|bulletinboard)$})) 
+    if (((($uri=~/^adm\//) && ($uri !~ m{/(?:smppg|bulletinboard|viewclasslist|aboutme)$})) 
 	 || (($uri=~/\.meta$/) && ($uri!~m|^uploaded/|) )) 
 	&& ($priv eq 'bre')) {
 	return 'F';
@@ -8454,7 +8481,11 @@ sub get_commblock_resources {
     my ($blocks) = @_;
     my %blockers = ();
     return %blockers unless ($env{'request.course.id'});
-    return %blockers if ($env{'user.priv.'.$env{'request.role'}} =~/evb\&([^\:]*)/);
+    my $courseurl = &courseid_to_courseurl($env{'request.course.id'});
+    if ($env{'request.course.sec'}) {
+        $courseurl .= '/'.$env{'request.course.sec'};
+    }
+    return %blockers if ($env{'user.priv.'.$env{'request.role'}.'.'.$courseurl} =~/evb\&([^\:]*)/);
     my %commblocks;
     if (ref($blocks) eq 'HASH') {
         %commblocks = %{$blocks};
@@ -8486,10 +8517,9 @@ sub get_commblock_resources {
             }
         } elsif ($block =~ /^firstaccess____(.+)$/) {
             my $item = $1;
-            my @to_test;
             if (ref($commblocks{$block}{'blocks'}) eq 'HASH') {
                 if (ref($commblocks{$block}{'blocks'}{'docs'}) eq 'HASH') {
-                    my @interval;
+                    my (@interval,$mapname);
                     my $type = 'map';
                     if ($item eq 'course') {
                         $type = 'course';
@@ -8498,40 +8528,16 @@ sub get_commblock_resources {
                         if ($item =~ /___\d+___/) {
                             $type = 'resource';
                             @interval=&EXT("resource.0.interval",$item);
-                            if (ref($navmap)) {
-                                my $res = $navmap->getBySymb($item);
-                                push(@to_test,$res);
-                            }
                         } else {
-                            my $mapsymb = &symbread($item,1);
-                            if ($mapsymb) {
-                                if (ref($navmap)) {
-                                    my $mapres = $navmap->getBySymb($mapsymb);
-                                    if (ref($mapres)) {
-                                        my $first = $mapres->map_start();
-                                        my $finish = $mapres->map_finish();
-                                        my $it = $navmap->getIterator($first,$finish,undef,0,0);
-                                        if (ref($it)) {
-                                            my $res;
-                                            while ($res = $it->next(undef,1)) {
-                                                next unless (ref($res));
-                                                my $symb = $res->symb();
-                                                next if (($symb eq $mapsymb) || ($symb eq ''));
-                                                @interval=&EXT("resource.0.interval",$symb);
-                                                if ($interval[1] eq 'map') {
-                                                    if ($res->answerable()) {
-                                                        push(@to_test,$res);
-                                                        last;
-                                                    }
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
+                            $mapname = &deversion($item);
+                            if (ref($navmap)) {
+                                my $timelimit = $navmap->get_mapparam(undef,$mapname,'0.interval');
+                                @interval = ($timelimit,'map');
                             }
                         }
                     }
-                    if ($interval[0] =~ /^\d+$/) {
+                    if ($interval[0] =~ /^(\d+)/) {
+                        my $timelimit = $1;
                         my $first_access;
                         if ($type eq 'resource') {
                             $first_access=&get_first_access($interval[1],$item);
@@ -8541,13 +8547,40 @@ sub get_commblock_resources {
                             $first_access=&get_first_access($interval[1]);
                         }
                         if ($first_access) {
-                            my $timesup = $first_access+$interval[0];
+                            my $timesup = $first_access+$timelimit;
                             if ($timesup > $now) {
                                 my $activeblock;
-                                foreach my $res (@to_test) {
-                                    if ($res->answerable()) {
-                                        $activeblock = 1;
-                                        last;
+                                if ($type eq 'resource') {
+                                    if (ref($navmap)) {
+                                        my $res = $navmap->getBySymb($item);
+                                        if ($res->answerable()) {
+                                            $activeblock = 1;
+                                        }
+                                    }
+                                } elsif ($type eq 'map') {
+                                    my $mapsymb = &symbread($mapname,1);
+                                    if (($mapsymb) && (ref($navmap))) {
+                                        my $mapres = $navmap->getBySymb($mapsymb);
+                                        if (ref($mapres)) {
+                                            my $first = $mapres->map_start();
+                                            my $finish = $mapres->map_finish();
+                                            my $it = $navmap->getIterator($first,$finish,undef,0,0);
+                                            if (ref($it)) {
+                                                my $res;
+                                                while ($res = $it->next(undef,1)) {
+                                                    next unless (ref($res));
+                                                    my $symb = $res->symb();
+                                                    next if (($symb eq $mapsymb) || ($symb eq ''));
+                                                    @interval=&EXT("resource.0.interval",$symb);
+                                                    if ($interval[1] eq 'map') {
+                                                        if ($res->answerable()) {
+                                                            $activeblock = 1;
+                                                            last;
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
                                     }
                                 }
                                 if ($activeblock) {
@@ -8577,8 +8610,12 @@ sub has_comm_blocking {
     my @blockers;
     return unless ($env{'request.course.id'});
     return unless ($priv eq 'bre');
-    return if ($env{'user.priv.'.$env{'request.role'}} =~/evb\&([^\:]*)/);
     return if ($env{'request.state'} eq 'construct');
+    my $courseurl = &courseid_to_courseurl($env{'request.course.id'});
+    if ($env{'request.course.sec'}) {
+        $courseurl .= '/'.$env{'request.course.sec'};
+    }
+    return if ($env{'user.priv.'.$env{'request.role'}.'.'.$courseurl} =~/evb\&([^\:]*)/);
     my %blockinfo;
     if (ref($blocks) eq 'HASH') {
         %blockinfo = &get_commblock_resources($blocks);
@@ -9946,13 +9983,14 @@ sub modifyuserauth {
              ' in domain '.$env{'request.role.domain'});  
     my $reply=&reply('encrypt:changeuserauth:'.$udom.':'.$uname.':'.$umode.':'.
 		     &escape($upass),$uhome);
+    my $ip = &get_requestor_ip();
     &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},
         'Authentication changed for '.$udom.', '.$uname.', '.$umode.
-         '(Remote '.$ENV{'REMOTE_ADDR'}.'): '.$reply);
+         '(Remote '.$ip.'): '.$reply);
     &log($udom,,$uname,$uhome,
         'Authentication changed by '.$env{'user.domain'}.', '.
                                      $env{'user.name'}.', '.$umode.
-         '(Remote '.$ENV{'REMOTE_ADDR'}.'): '.$reply);
+         '(Remote '.$ip.'): '.$reply);
     unless ($reply eq 'ok') {
         &logthis('Authentication mode error: '.$reply);
 	return 'error: '.$reply;
@@ -10468,7 +10506,7 @@ sub store_userdata {
             if (($uhome eq '') || ($uhome eq 'no_host')) {
                 $result = 'error: no_host';
             } else {
-                $storehash->{'ip'} = $ENV{'REMOTE_ADDR'};
+                $storehash->{'ip'} = &get_requestor_ip();
                 $storehash->{'host'} = $perlvar{'lonHostID'};
 
                 my $namevalue='';
@@ -13342,9 +13380,12 @@ sub default_login_domain {
 }
 
 sub shared_institution {
-    my ($dom) = @_;
+    my ($dom,$lonhost) = @_;
+    if ($lonhost eq '') {
+        $lonhost = $perlvar{'lonHostID'};
+    }
     my $same_intdom;
-    my $hostintdom = &internet_dom($perlvar{'lonHostID'});
+    my $hostintdom = &internet_dom($lonhost);
     if ($hostintdom ne '') {
         my %iphost = &get_iphost();
         my $primary_id = &domain($dom,'primary');
@@ -13401,6 +13442,17 @@ sub uses_sts {
     return;
 }
 
+sub get_requestor_ip {
+    my ($r,$nolookup,$noproxy) = @_;
+    my $from_ip;
+    if (ref($r)) {
+        $from_ip = $r->get_remote_host($nolookup);
+    } else {
+        $from_ip = $ENV{'REMOTE_ADDR'};
+    }
+    return $from_ip;
+}
+
 # ------------------------------------------------------------- Declutters URLs
 
 sub declutter {