--- loncom/lonnet/perl/lonnet.pm	2016/07/25 19:50:44	1.1315
+++ loncom/lonnet/perl/lonnet.pm	2016/10/05 21:21:06	1.1327
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1315 2016/07/25 19:50:44 raeburn Exp $
+# $Id: lonnet.pm,v 1.1327 2016/10/05 21:21:06 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -235,9 +235,11 @@ sub get_servercerts_info {
     if (grep { $_ eq $lonhost } &current_machine_ids()) {
         $uselocal = 1;
     }
-    if (($context ne 'cgi') || $uselocal) {
+    if (($context ne 'cgi') && ($uselocal)) {
         my $distro = (split(/\:/,&get_server_distarch($lonhost)))[0];
-        if ($distro =~ /^(?:centos|redhat|scientific)(\d+)$/) {
+        if ($distro eq '') {
+            $uselocal = 0;
+        } elsif ($distro =~ /^(?:centos|redhat|scientific)(\d+)$/) {
             if ($1 < 6) {
                 $uselocal = 0;
             }
@@ -2242,7 +2244,8 @@ sub get_domain_defaults {
                                   'requestcourses','inststatus',
                                   'coursedefaults','usersessions',
                                   'requestauthor','selfenrollment',
-                                  'coursecategories','ssl','autoenroll'],$domain);
+                                  'coursecategories','ssl','autoenroll',
+                                  'trust'],$domain);
     my @coursetypes = ('official','unofficial','community','textbook','placement');
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
@@ -2376,6 +2379,14 @@ sub get_domain_defaults {
             $domdefaults{'connect'} = $domconfig{'ssl'}{'connect'};
         }
     }
+    if (ref($domconfig{'trust'}) eq 'HASH') {
+        my @prefixes = qw(content shared enroll othcoau coaurem domroles catalog reqcrs msg);
+        foreach my $prefix (@prefixes) {
+            if (ref($domconfig{'trust'}{$prefix}) eq 'HASH') {
+                $domdefaults{'trust'.$prefix} = $domconfig{'trust'}{$prefix};
+            }
+        }
+    }
     if (ref($domconfig{'autoenroll'}) eq 'HASH') {
         $domdefaults{'autofailsafe'} = $domconfig{'autoenroll'}{'autofailsafe'};
     }
@@ -2635,21 +2646,23 @@ sub make_key {
 sub devalidate_cache_new {
     my ($name,$id,$debug) = @_;
     if ($debug) { &Apache::lonnet::logthis("deleting $name:$id"); }
+    my $remembered_id=$name.':'.$id;
     $id=&make_key($name,$id);
     $memcache->delete($id);
-    delete($remembered{$id});
-    delete($accessed{$id});
+    delete($remembered{$remembered_id});
+    delete($accessed{$remembered_id});
 }
 
 sub is_cached_new {
     my ($name,$id,$debug) = @_;
-    $id=&make_key($name,$id);
-    if (exists($remembered{$id})) {
-	if ($debug) { &Apache::lonnet::logthis("Early return $id of $remembered{$id} "); }
-	$accessed{$id}=[&gettimeofday()];
+    my $remembered_id=$name.':'.$id; # this is to avoid make_key (which is slow) whenever possible
+    if (exists($remembered{$remembered_id})) {
+	if ($debug) { &Apache::lonnet::logthis("Early return $remembered_id of $remembered{$remembered_id} "); }
+	$accessed{$remembered_id}=[&gettimeofday()];
 	$hits++;
-	return ($remembered{$id},1);
+	return ($remembered{$remembered_id},1);
     }
+    $id=&make_key($name,$id);
     my $value = $memcache->get($id);
     if (!(defined($value))) {
 	if ($debug) { &Apache::lonnet::logthis("getting $id is not defined"); }
@@ -2659,13 +2672,14 @@ sub is_cached_new {
 	if ($debug) { &Apache::lonnet::logthis("getting $id is __undef__"); }
 	$value=undef;
     }
-    &make_room($id,$value,$debug);
+    &make_room($remembered_id,$value,$debug);
     if ($debug) { &Apache::lonnet::logthis("getting $id is $value"); }
     return ($value,1);
 }
 
 sub do_cache_new {
     my ($name,$id,$value,$time,$debug) = @_;
+    my $remembered_id=$name.':'.$id;
     $id=&make_key($name,$id);
     my $setvalue=$value;
     if (!defined($setvalue)) {
@@ -2681,17 +2695,17 @@ sub do_cache_new {
 	$memcache->disconnect_all();
     }
     # need to make a copy of $value
-    &make_room($id,$value,$debug);
+    &make_room($remembered_id,$value,$debug);
     return $value;
 }
 
 sub make_room {
-    my ($id,$value,$debug)=@_;
+    my ($remembered_id,$value,$debug)=@_;
 
-    $remembered{$id}= (ref($value)) ? &Storable::dclone($value)
+    $remembered{$remembered_id}= (ref($value)) ? &Storable::dclone($value)
                                     : $value;
     if ($to_remember<0) { return; }
-    $accessed{$id}=[&gettimeofday()];
+    $accessed{$remembered_id}=[&gettimeofday()];
     if (scalar(keys(%remembered)) <= $to_remember) { return; }
     my $to_kick;
     my $max_time=0;
@@ -4094,7 +4108,7 @@ sub flushcourselogs {
         }
     }
 #
-# Reverse lookup of domain roles (dc, ad, li, sc, au)
+# Reverse lookup of domain roles (dc, ad, li, sc, dh, au)
 #
     my %domrolebuffer = ();
     foreach my $entry (keys(%domainrolehash)) {
@@ -4109,10 +4123,19 @@ sub flushcourselogs {
         delete $domainrolehash{$entry};
     }
     foreach my $dom (keys(%domrolebuffer)) {
-	my %servers = &get_servers($dom,'library');
+	my %servers;
+	if (defined(&domain($dom,'primary'))) {
+	    my $primary=&domain($dom,'primary');
+	    my $hostname=&hostname($primary);
+	    $servers{$primary} = $hostname;
+	} else { 
+	    %servers = &get_servers($dom,'library');
+	}
 	foreach my $tryserver (keys(%servers)) {
-	    unless (&reply('domroleput:'.$dom.':'.
-			   $domrolebuffer{$dom},$tryserver) eq 'ok') {
+	    if (&reply('domroleput:'.$dom.':'.
+		       $domrolebuffer{$dom},$tryserver) eq 'ok') {
+		last;
+	    } else {  
 		&logthis('Put of domain roles failed for '.$dom.' and  '.$tryserver);
 	    }
         }
@@ -4232,7 +4255,7 @@ sub userrolelog {
          {$trole.':'.$username.':'.$domain.':'.$env{'user.name'}.':'.$env{'user.domain'}.':'}
                     =$tend.':'.$tstart;
     }
-    if ($trole =~ /^(dc|ad|li|au|dg|sc)/ ) {
+    if ($trole =~ /^(dc|ad|li|au|dg|sc|dh)/ ) {
        my (undef,$rudom,$runame,$rsec)=split(/\//,$area);
        $domainrolehash
          {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec}
@@ -5829,15 +5852,19 @@ sub check_adhoc_privs {
 }
 
 sub set_adhoc_privileges {
-# role can be cc or ca
+# role can be cc, ca, or cr/<dom>/<dom>-domainconfig/role
     my ($dcdom,$pickedcourse,$role,$caller) = @_;
     my $area = '/'.$dcdom.'/'.$pickedcourse;
     my $spec = $role.'.'.$area;
     my %userroles = &set_arearole($role,$area,'','',$env{'user.domain'},
                                   $env{'user.name'},1);
-    my %ccrole = ();
-    &standard_roleprivs(\%ccrole,$role,$dcdom,$spec,$pickedcourse,$area);
-    my ($author,$adv)= &set_userprivs(\%userroles,\%ccrole);
+    my %rolehash = ();
+    if ($role =~ m{^cr/$dcdom/$dcdom\Q-domainconfig\E/}) {
+        &custom_roleprivs(\%rolehash,$role,$dcdom,$pickedcourse,$spec,$area);
+    } else {
+        &standard_roleprivs(\%rolehash,$role,$dcdom,$spec,$pickedcourse,$area);
+    }
+    my ($author,$adv)= &set_userprivs(\%userroles,\%rolehash);
     &appenv(\%userroles,[$role,'cm']);
     &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role);
     unless ($caller eq 'constructaccess' && $env{'request.course.id'}) {
@@ -7413,7 +7440,7 @@ sub constructaccess {
     my ($ownername,$ownerdomain,$ownerhome);
 
     ($ownerdomain,$ownername) =
-        ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)(?:/daxepage|/daxeopen)?/priv/($match_domain)/($match_username)/});
+        ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)(?:/daxepage|/daxeopen)?/priv/($match_domain)/($match_username)(?:/|$)});
 
 # The URL does not really point to any authorspace, forget it
     unless (($ownername) && ($ownerdomain)) { return ''; }
@@ -7761,7 +7788,7 @@ sub get_symb_from_alias {
 
 sub definerole {
   if (allowed('mcr','/')) {
-    my ($rolename,$sysrole,$domrole,$courole)=@_;
+    my ($rolename,$sysrole,$domrole,$courole,$uname,$udom)=@_;
     foreach my $role (split(':',$sysrole)) {
 	my ($crole,$cqual)=split(/\&/,$role);
         if ($pr{'cr:s'}!~/\Q$crole\E/) { return "refused:s:$crole"; }
@@ -7789,11 +7816,19 @@ sub definerole {
             }
         }
     }
+    my $uhome;
+    if (($uname ne '') && ($udom ne '')) {
+        $uhome = &homeserver($uname,$udom);
+        return $uhome if ($uhome eq 'no_host');
+    } else {
+        $uname = $env{'user.name'};
+        $udom = $env{'user.domain'};
+        $uhome = $env{'user.home'};
+    }
     my $command="encrypt:rolesput:$env{'user.domain'}:$env{'user.name'}:".
-                "$env{'user.domain'}:$env{'user.name'}:".
-	        "rolesdef_$rolename=".
+                "$udom:$uname:rolesdef_$rolename=".
                 escape($sysrole.'_'.$domrole.'_'.$courole);
-    return reply($command,$env{'user.home'});
+    return reply($command,$uhome);
   } else {
     return 'refused';
   }
@@ -7875,10 +7910,12 @@ sub update_allusers_table {
 
 sub fetch_enrollment_query {
     my ($context,$affiliatesref,$replyref,$dom,$cnum) = @_;
-    my $homeserver;
+    my ($homeserver,$sleep,$loopmax);
     my $maxtries = 1;
     if ($context eq 'automated') {
         $homeserver = $perlvar{'lonHostID'};
+        $sleep = 2;
+        $loopmax = 100;
         $maxtries = 10; # will wait for up to 2000s for retrieval of classlist data before timeout
     } else {
         $homeserver = &homeserver($cnum,$dom);
@@ -7896,17 +7933,17 @@ sub fetch_enrollment_query {
         &logthis('fetch_enrollment_query: invalid queryid: '.$queryid.' for host: '.$host.' and homeserver: '.$homeserver.' context: '.$context.' '.$cnum); 
         return 'error: '.$queryid;
     }
-    my $reply = &get_query_reply($queryid);
+    my $reply = &get_query_reply($queryid,$sleep,$loopmax);
     my $tries = 1;
     while (($reply=~/^timeout/) && ($tries < $maxtries)) {
-        $reply = &get_query_reply($queryid);
+        $reply = &get_query_reply($queryid,$sleep,$loopmax);
         $tries ++;
     }
     if ( ($reply =~/^timeout/) || ($reply =~/^error/) ) {
         &logthis('fetch_enrollment_query error: '.$reply.' for '.$dom.' '.$env{'user.name'}.' for '.$queryid.' context: '.$context.' '.$cnum.' maxtries: '.$maxtries.' tries: '.$tries);
     } else {
         my @responses = split(/:/,$reply);
-        if ($homeserver eq $perlvar{'lonHostID'}) {
+        if (grep { $_ eq $homeserver } &current_machine_ids()) {
             foreach my $line (@responses) {
                 my ($key,$value) = split(/=/,$line,2);
                 $$replyref{$key} = $value;
@@ -7941,11 +7978,17 @@ sub fetch_enrollment_query {
 }
 
 sub get_query_reply {
-    my $queryid=shift;
+    my ($queryid,$sleep,$loopmax) = @_;;
+    if (($sleep eq '') || ($sleep !~ /^\d+\.?\d*$/)) {
+        $sleep = 0.2;
+    }
+    if (($loopmax eq '') || ($loopmax =~ /\D/)) {
+        $loopmax = 100;
+    }
     my $replyfile=LONCAPA::tempdir().$queryid;
     my $reply='';
-    for (1..100) {
-	sleep(0.2);
+    for (1..$loopmax) {
+	sleep($sleep);
         if (-e $replyfile.'.end') {
 	    if (open(my $fh,$replyfile)) {
 		$reply = join('',<$fh>);
@@ -8834,7 +8877,7 @@ sub assignrole {
             &courserolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag,
                            $selfenroll,$context);
         } elsif (($role eq 'li') || ($role eq 'dg') || ($role eq 'sc') ||
-                 ($role eq 'au') || ($role eq 'dc')) {
+                 ($role eq 'au') || ($role eq 'dc') || ($role eq 'dh')) {
             &domainrolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag,
                            $context);
         } elsif (($role eq 'ca') || ($role eq 'aa')) {
@@ -10194,7 +10237,24 @@ sub dirlist {
             foreach my $user (sort(keys(%allusers))) {
                 push(@alluserslist,$user.'&user');
             }
-            return (\@alluserslist);
+
+            if (!%listerror) {
+                # no errors
+                return (\@alluserslist);
+            } elsif (scalar(keys(%servers)) == 1) {
+                # one library server, one error 
+                my ($key) = keys(%listerror);
+                return (\@alluserslist, $listerror{$key});
+            } elsif ( grep { $_ eq 'con_lost' } values(%listerror) ) {
+                # con_lost indicates that we might miss data from at least one
+                # library server
+                return (\@alluserslist, 'con_lost');
+            } else {
+                # multiple library servers and no con_lost -> data should be
+                # complete. 
+                return (\@alluserslist);
+            }
+
         } else {
             return ([],'missing username');
         }
@@ -13708,9 +13768,10 @@ in which case the null string is returne
 
 =item *
 
-definerole($rolename,$sysrole,$domrole,$courole) : define role; define a custom
-role rolename set privileges in format of lonTabs/roles.tab for system, domain,
-and course level
+definerole($rolename,$sysrole,$domrole,$courole,$uname,$udom) : define role;
+define a custom role rolename set privileges in format of lonTabs/roles.tab
+for system, domain, and course level. $uname and $udom are optional (current
+user's username and domain will be used when either of $uname or $udom are absent.
 
 =item *