--- loncom/lonnet/perl/lonnet.pm	2016/06/19 00:19:24	1.1311
+++ loncom/lonnet/perl/lonnet.pm	2017/10/07 00:50:52	1.1356
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1311 2016/06/19 00:19:24 raeburn Exp $
+# $Id: lonnet.pm,v 1.1356 2017/10/07 00:50:52 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -71,7 +71,6 @@ delayed.
 package Apache::lonnet;
 
 use strict;
-use LWP::UserAgent();
 use HTTP::Date;
 use Image::Magick;
 
@@ -101,6 +100,7 @@ use LONCAPA qw(:DEFAULT :match);
 use LONCAPA::Configuration;
 use LONCAPA::lonmetadata;
 use LONCAPA::Lond;
+use LONCAPA::LWPReq;
 
 use File::Copy;
 
@@ -229,6 +229,52 @@ sub get_server_distarch {
     return;
 }
 
+sub get_servercerts_info {
+    my ($lonhost,$context) = @_;
+    my ($rep,$uselocal);
+    if (grep { $_ eq $lonhost } &current_machine_ids()) {
+        $uselocal = 1;
+    }
+    if (($context ne 'cgi') && ($uselocal)) {
+        my $distro = (split(/\:/,&get_server_distarch($lonhost)))[0];
+        if ($distro eq '') {
+            $uselocal = 0;
+        } elsif ($distro =~ /^(?:centos|redhat|scientific)(\d+)$/) {
+            if ($1 < 6) {
+                $uselocal = 0;
+            }
+        }  elsif ($distro =~ /^(?:sles)(\d+)$/) {
+            if ($1 < 12) {
+                $uselocal = 0;
+            }
+        }
+    }
+    if ($uselocal) {
+        $rep = LONCAPA::Lond::server_certs(\%perlvar);
+    } else {
+        $rep=&reply('servercerts',$lonhost);
+    }
+    my ($result,%returnhash);
+    if (defined($lonhost)) {
+        if (!defined(&hostname($lonhost))) {
+            return;
+        }
+    }
+    if (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') ||
+        ($rep eq 'unknown_cmd')) {
+        $result = $rep;
+    } else {
+        $result = 'ok';
+        my @pairs=split(/\&/,$rep);
+        foreach my $item (@pairs) {
+            my ($key,$value)=split(/=/,$item,2);
+            my $what = &unescape($key);
+            $returnhash{$what}=&thaw_unescape($value);
+        }
+    }
+    return ($result,\%returnhash);
+}
+
 sub get_server_loncaparev {
     my ($dom,$lonhost,$ignore_cache,$caller) = @_;
     if (defined($lonhost)) {
@@ -263,13 +309,11 @@ sub get_server_loncaparev {
             $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);
+                    my $response=&LONCAPA::LWPReq::makerequest($lonhost,$request,'',\%perlvar,4,1);
                     unless ($response->is_error()) {
                         my $content = $response->content;
                         if ($content =~ /<p>VERSION\:\s*([\w.\-]+)<\/p>/) {
@@ -606,12 +650,25 @@ sub transfer_profile_to_env {
 
 # ---------------------------------------------------- Check for valid session 
 sub check_for_valid_session {
-    my ($r,$name,$userhashref) = @_;
+    my ($r,$name,$userhashref,$domref) = @_;
     my %cookies=CGI::Cookie->parse($r->header_in('Cookie'));
+    my ($linkname,$pubname);
     if ($name eq '') {
         $name = 'lonID';
+        $linkname = 'lonLinkID';
+        $pubname = 'lonPubID';
     }
     my $lonid=$cookies{$name};
+    if (!$lonid) {
+        if (($name eq 'lonID') && ($ENV{'SERVER_PORT'} != 443) && ($linkname)) {
+            $lonid=$cookies{$linkname};
+        }
+        if (!$lonid) {
+            if (($name eq 'lonID') && ($pubname)) {
+                $lonid=$cookies{$pubname};
+            }
+        }
+    }
     return undef if (!$lonid);
 
     my $handle=&LONCAPA::clean_handle($lonid->value);
@@ -621,7 +678,16 @@ sub check_for_valid_session {
     } else {
         $lonidsdir=$r->dir_config('lonIDsDir');
     }
-    return undef if (!-e "$lonidsdir/$handle.id");
+    if (!-e "$lonidsdir/$handle.id") {
+        if ((ref($domref)) && ($name eq 'lonID') && 
+            ($handle =~ /^($match_username)\_\d+\_($match_domain)\_(.+)$/)) {
+            my ($possuname,$possudom,$possuhome) = ($1,$2,$3);
+            if ((&domain($possudom) ne '') && (&homeserver($possuname,$possudom) eq $possuhome)) {
+                $$domref = $possudom;
+            }
+        }
+        return undef;
+    }
 
     my $opened = open(my $idf,'+<',"$lonidsdir/$handle.id");
     return undef if (!$opened);
@@ -1007,7 +1073,7 @@ sub choose_server {
     if ($login_host ne '') {
         $hostname = &hostname($login_host);
     }
-    return ($login_host,$hostname,$portal_path,$isredirect);
+    return ($login_host,$hostname,$portal_path,$isredirect,$lowest_load);
 }
 
 # --------------------------------------------- Try to change a user's password
@@ -1279,7 +1345,7 @@ sub get_lonbalancer_config {
 }
 
 sub check_loadbalancing {
-    my ($uname,$udom) = @_;
+    my ($uname,$udom,$caller) = @_;
     my ($is_balancer,$currtargets,$currrules,$dom_in_use,$homeintdom,
         $rule_in_effect,$offloadto,$otherserver);
     my $lonhost = $perlvar{'lonHostID'};
@@ -1430,13 +1496,15 @@ sub check_loadbalancing {
                 }
             }
         }
-        if (($otherserver ne '') && (grep(/^\Q$otherserver\E$/,@hosts))) {
-            $is_balancer = 0;
-            if ($uname ne '' && $udom ne '') {
-                if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) {
+        unless ($caller eq 'login') {
+            if (($otherserver ne '') && (grep(/^\Q$otherserver\E$/,@hosts))) {
+                $is_balancer = 0;
+                if ($uname ne '' && $udom ne '') {
+                    if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) {
                     
-                    &appenv({'user.loadbalexempt'     => $lonhost,  
-                             'user.loadbalcheck.time' => time});
+                        &appenv({'user.loadbalexempt'     => $lonhost,  
+                                 'user.loadbalcheck.time' => time});
+                    }
                 }
             }
         }
@@ -1539,6 +1607,100 @@ sub internet_dom_servers {
     return %uniqservers;
 }
 
+sub trusted_domains {
+    my ($cmdtype,$calldom) = @_;
+    my ($trusted,$untrusted);
+    if (&domain($calldom) eq '') {
+        return ($trusted,$untrusted);
+    }
+    unless ($cmdtype =~ /^(content|shared|enroll|coaurem|domroles|catalog|reqcrs|msg)$/) {
+        return ($trusted,$untrusted);
+    }
+    my $callprimary = &domain($calldom,'primary');
+    my $intcalldom = &Apache::lonnet::internet_dom($callprimary);
+    if ($intcalldom eq '') {
+        return ($trusted,$untrusted);
+    }
+
+    my ($trustconfig,$cached)=&Apache::lonnet::is_cached_new('trust',$calldom);
+    unless (defined($cached)) {
+        my %domconfig = &Apache::lonnet::get_dom('configuration',['trust'],$calldom);
+        &Apache::lonnet::do_cache_new('trust',$calldom,$domconfig{'trust'},3600);
+        $trustconfig = $domconfig{'trust'};
+    }
+    if (ref($trustconfig)) {
+        my (%possexc,%possinc,@allexc,@allinc); 
+        if (ref($trustconfig->{$cmdtype}) eq 'HASH') {
+            if (ref($trustconfig->{$cmdtype}->{'exc'}) eq 'ARRAY') {
+                map { $possexc{$_} = 1; } @{$trustconfig->{$cmdtype}->{'exc'}}; 
+            }
+            if (ref($trustconfig->{$cmdtype}->{'inc'}) eq 'ARRAY') {
+                map { $possinc{$_} = 1; } @{$trustconfig->{$cmdtype}->{'inc'}};
+            }
+        }
+        if (keys(%possexc)) {
+            if (keys(%possinc)) {
+                foreach my $key (sort(keys(%possexc))) {
+                    next if ($key eq $intcalldom);
+                    unless ($possinc{$key}) {
+                        push(@allexc,$key);
+                    }
+                }
+            } else {
+                @allexc = sort(keys(%possexc));
+            }
+        }
+        if (keys(%possinc)) {
+            $possinc{$intcalldom} = 1;
+            @allinc = sort(keys(%possinc));
+        }
+        if ((@allexc > 0) || (@allinc > 0)) {
+            my %doms_by_intdom;
+            my %allintdoms = &all_host_intdom();
+            my %alldoms = &all_host_domain();
+            foreach my $key (%allintdoms) {
+                if (ref($doms_by_intdom{$allintdoms{$key}}) eq 'ARRAY') {
+                    unless (grep(/^\Q$alldoms{$key}\E$/,@{$doms_by_intdom{$allintdoms{$key}}})) {
+                        push(@{$doms_by_intdom{$allintdoms{$key}}},$alldoms{$key});
+                    }
+                } else {
+                    $doms_by_intdom{$allintdoms{$key}} = [$alldoms{$key}]; 
+                }
+            }
+            foreach my $exc (@allexc) {
+                if (ref($doms_by_intdom{$exc}) eq 'ARRAY') {
+                    $untrusted = $doms_by_intdom{$exc};
+                }
+            }
+            foreach my $inc (@allinc) {
+                if (ref($doms_by_intdom{$inc}) eq 'ARRAY') {
+                    $trusted = $doms_by_intdom{$inc};
+                }
+            }
+        }
+    }
+    return ($trusted,$untrusted);
+}
+
+sub will_trust {
+    my ($cmdtype,$domain,$possdom) = @_;
+    return 1 if ($domain eq $possdom);
+    my ($trustedref,$untrustedref) = &trusted_domains($cmdtype,$possdom);
+    my $willtrust; 
+    if ((ref($trustedref) eq 'ARRAY') && (@{$trustedref} > 0)) {
+        if (grep(/^\Q$domain\E$/,@{$trustedref})) {
+            $willtrust = 1;
+        }
+    } elsif ((ref($untrustedref) eq 'ARRAY') && (@{$untrustedref} > 0)) {
+        unless (grep(/^\Q$domain\E$/,@{$untrustedref})) {
+            $willtrust = 1;
+        }
+    } else {
+        $willtrust = 1;
+    }
+    return $willtrust;
+}
+
 # ---------------------- Find the homebase for a user from domain's lib servers
 
 my %homecache;
@@ -1788,7 +1950,12 @@ sub get_dom {
         }
     }
     if ($udom && $uhome && ($uhome ne 'no_host')) {
-        my $rep=&reply("getdom:$udom:$namespace:$items",$uhome);
+        my $rep;
+        if ($namespace =~ /^enc/) {
+            $rep=&reply("encrypt:egetdom:$udom:$namespace:$items",$uhome);
+        } else {
+            $rep=&reply("getdom:$udom:$namespace:$items",$uhome);
+        }
         my %returnhash;
         if ($rep eq '' || $rep =~ /^error: 2 /) {
             return %returnhash;
@@ -1832,7 +1999,11 @@ sub put_dom {
             $items.=&escape($item).'='.&freeze_escape($$storehash{$item}).'&';
         }
         $items=~s/\&$//;
-        return &reply("putdom:$udom:$namespace:$items",$uhome);
+        if ($namespace =~ /^enc/) {
+            return &reply("encrypt:putdom:$udom:$namespace:$items",$uhome);
+        } else {
+            return &reply("putdom:$udom:$namespace:$items",$uhome);
+        }
     } else {
         &logthis("put_dom failed - no homeserver and/or domain");
     }
@@ -1929,7 +2100,7 @@ sub inst_directory_query {
 			   &escape($srch->{'srchtype'}),$homeserver);
 	my $host=&hostname($homeserver);
 	if ($queryid !~/^\Q$host\E\_/) {
-	    &logthis('instituional directory search invalid queryid: '.$queryid.' for host: '.$homeserver.'in domain '.$udom);
+	    &logthis('institutional directory search invalid queryid: '.$queryid.' for host: '.$homeserver.' in domain '.$udom);
 	    return;
 	}
 	my $response = &get_query_reply($queryid);
@@ -2202,7 +2373,8 @@ sub get_domain_defaults {
                                   'requestcourses','inststatus',
                                   'coursedefaults','usersessions',
                                   'requestauthor','selfenrollment',
-                                  'coursecategories'],$domain);
+                                  'coursecategories','ssl','autoenroll',
+                                  'trust','helpsettings'],$domain);
     my @coursetypes = ('official','unofficial','community','textbook','placement');
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
@@ -2211,6 +2383,9 @@ sub get_domain_defaults {
         $domdefaults{'timezone_def'} = $domconfig{'defaults'}{'timezone_def'};
         $domdefaults{'datelocale_def'} = $domconfig{'defaults'}{'datelocale_def'};
         $domdefaults{'portal_def'} = $domconfig{'defaults'}{'portal_def'};
+        $domdefaults{'intauth_cost'} = $domconfig{'defaults'}{'intauth_cost'};
+        $domdefaults{'intauth_switch'} = $domconfig{'defaults'}{'intauth_switch'};
+        $domdefaults{'intauth_check'} = $domconfig{'defaults'}{'intauth_check'};
     } else {
         $domdefaults{'lang_def'} = &domain($domain,'lang_def');
         $domdefaults{'auth_def'} = &domain($domain,'auth_def');
@@ -2278,6 +2453,9 @@ sub get_domain_defaults {
         } elsif ($domconfig{'coursedefaults'}{'canclone'}) {
             $domdefaults{'canclone'}=$domconfig{'coursedefaults'}{'canclone'};
         }
+        if ($domconfig{'coursedefaults'}{'texengine'}) {
+            $domdefaults{'texengine'} = $domconfig{'coursedefaults'}{'texengine'};
+        } 
     }
     if (ref($domconfig{'usersessions'}) eq 'HASH') {
         if (ref($domconfig{'usersessions'}{'remote'}) eq 'HASH') {
@@ -2328,6 +2506,34 @@ sub get_domain_defaults {
             $domdefaults{'catunauth'} = $domconfig{'coursecategories'}{'unauth'};
         }
     }
+    if (ref($domconfig{'ssl'}) eq 'HASH') {
+        if (ref($domconfig{'ssl'}{'replication'}) eq 'HASH') {
+            $domdefaults{'replication'} = $domconfig{'ssl'}{'replication'};
+        }
+        if (ref($domconfig{'ssl'}{'connto'}) eq 'HASH') {
+            $domdefaults{'connect'} = $domconfig{'ssl'}{'connto'};
+        }
+        if (ref($domconfig{'ssl'}{'connfrom'}) eq 'HASH') {
+            $domdefaults{'connect'} = $domconfig{'ssl'}{'connfrom'};
+        }
+    }
+    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'};
+    }
+    if (ref($domconfig{'helpsettings'}) eq 'HASH') {
+        $domdefaults{'submitbugs'} = $domconfig{'helpsettings'}{'submitbugs'};
+        if (ref($domconfig{'helpsettings'}{'adhoc'}) eq 'HASH') {
+            $domdefaults{'adhocroles'} = $domconfig{'helpsettings'}{'adhoc'};
+        }
+    }
     &do_cache_new('domdefaults',$domain,\%domdefaults,$cachetime);
     return %domdefaults;
 }
@@ -2584,21 +2790,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"); }
@@ -2608,13 +2816,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)) {
@@ -2630,17 +2839,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;
@@ -2852,9 +3061,13 @@ sub repcopy {
 		   mkdir($path,0777);
                }
            }
-           my $ua=new LWP::UserAgent;
            my $request=new HTTP::Request('GET',"$remoteurl");
-           my $response=$ua->request($request,$transname);
+           my $response;
+           if ($remoteurl =~ m{/raw/}) {
+               $response=&LONCAPA::LWPReq::makerequest($home,$request,$transname,\%perlvar,'',0,1);
+           } else {
+               $response=&LONCAPA::LWPReq::makerequest($home,$request,$transname,\%perlvar,'',1);
+           }
            if ($response->is_error()) {
 	       unlink($transname);
                my $message=$response->status_line;
@@ -2864,7 +3077,12 @@ sub repcopy {
            } else {
 	       if ($remoteurl!~/\.meta$/) {
                   my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta');
-                  my $mresponse=$ua->request($mrequest,$filename.'.meta');
+                  my $mresponse;
+                  if ($remoteurl =~ m{/raw/}) {
+                      $mresponse = &LONCAPA::LWPReq::makerequest($home,$mrequest,$filename.'.meta',\%perlvar,'',0,1);
+                  } else {
+                      $mresponse = &LONCAPA::LWPReq::makerequest($home,$mrequest,$filename.'.meta',\%perlvar,'',1);
+                  }
                   if ($mresponse->is_error()) {
 		      unlink($filename.'.meta');
                       &logthis(
@@ -2927,7 +3145,6 @@ sub absolute_url {
 sub ssi {
 
     my ($fn,%form)=@_;
-    my $ua=new LWP::UserAgent;
     my $request;
 
     $form{'no_update_last_known'}=1;
@@ -2945,22 +3162,20 @@ sub ssi {
     }
 
     $request->header(Cookie => $ENV{'HTTP_COOKIE'});
-    my $response= $ua->request($request);
-    my $content = $response->content;
-
+    my $lonhost = $perlvar{'lonHostID'};
+    my $response= &LONCAPA::LWPReq::makerequest($lonhost,$request,'',\%perlvar);
 
     if (wantarray) {
-	return ($content, $response);
+	return ($response->content, $response);
     } else {
-	return $content;
+	return $response->content;
     }
 }
 
 sub externalssi {
     my ($url)=@_;
-    my $ua=new LWP::UserAgent;
     my $request=new HTTP::Request('GET',$url);
-    my $response=$ua->request($request);
+    my $response = &LONCAPA::LWPReq::makerequest('',$request,'',\%perlvar);
     if (wantarray) {
         return ($response->content, $response);
     } else {
@@ -2968,6 +3183,71 @@ sub externalssi {
     }
 }
 
+
+# If the local copy of a replicated resource is outdated, trigger a  
+# connection from the homeserver to flush the delayed queue. If no update 
+# happens, remove local copies of outdated resource (and corresponding
+# metadata file).
+
+sub remove_stale_resfile {
+    my ($url) = @_;
+    my $removed;
+    if ($url=~m{^/res/($match_domain)/($match_username)/}) {
+        my $audom = $1;
+        my $auname = $2;
+        unless (($url =~ /\.\d+\.\w+$/) || ($url =~ m{^/res/lib/templates/})) {
+            my $homeserver = &homeserver($auname,$audom);
+            unless (($homeserver eq 'no_host') ||
+                    (grep { $_ eq $homeserver } &current_machine_ids())) {
+                my $fname = &filelocation('',$url);
+                if (-e $fname) {
+                    my $protocol = $protocol{$homeserver};
+                    $protocol = 'http' if ($protocol ne 'https');
+                    my $hostname = &hostname($homeserver);
+                    if ($hostname) {
+                        my $uri = &declutter($url);
+                        my $request=new HTTP::Request('HEAD',$protocol.'://'.$hostname.'/raw/'.$uri);
+                        my $response = &LONCAPA::LWPReq::makerequest($homeserver,$request,'',\%perlvar,5,0,1);
+                        if ($response->is_success()) {
+                            my $remmodtime = &HTTP::Date::str2time( $response->header('Last-modified') );
+                            my $locmodtime = (stat($fname))[9];
+                            if ($locmodtime < $remmodtime) {
+                                my $stale;
+                                my $answer = &reply('pong',$homeserver);
+                                if ($answer eq $homeserver.':'.$perlvar{'lonHostID'}) {
+                                    sleep(0.2);
+                                    $locmodtime = (stat($fname))[9];
+                                    if ($locmodtime < $remmodtime) {
+                                        my $posstransfer = $fname.'.in.transfer';
+                                        if ((-e $posstransfer) && ($remmodtime < (stat($posstransfer))[9])) {
+                                            $removed = 1;
+                                        } else {
+                                            $stale = 1;
+                                        }
+                                    } else {
+                                        $removed = 1;
+                                    }
+                                } else {
+                                    $stale = 1;
+                                }
+                                if ($stale) {
+                                    unlink($fname);
+                                    if ($uri!~/\.meta$/) {
+                                        unlink($fname.'.meta');
+                                    }
+                                    &reply("unsub:$fname",$homeserver);
+                                    $removed = 1;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return $removed;
+}
+
 # -------------------------------- Allow a /uploaded/ URI to be vouched for
 
 sub allowuploaded {
@@ -3106,7 +3386,7 @@ sub can_edit_resource {
                         $forceedit = 1;
                     }
                     $cfile = $resurl;
-                } elsif ($resurl =~ m{^/adm/wrapper/adm/$cdom/$cnum/\d+/exttools?$}) {
+                } elsif ($resurl =~ m{^/adm/wrapper/adm/$cdom/$cnum/\d+/ext\.tool$}) {
                     $incourse = 1;
                     if ($env{'form.forceedit'}) {
                         $forceview = 1;
@@ -3138,7 +3418,7 @@ sub can_edit_resource {
                         $forceedit = 1;
                     }
                     $cfile = $resurl;
-            } elsif (($resurl =~ m{^/adm/wrapper/adm/$cdom/$cnum/\d+/exttools?$}) && ($env{'form.folderpath'} =~ /^supplemental/)) {
+            } elsif (($resurl =~ m{^/adm/wrapper/adm/$cdom/$cnum/\d+/ext\.tool$}) && ($env{'form.folderpath'} =~ /^supplemental/)) {
                 $incourse = 1;
                 if ($env{'form.forceedit'}) {
                     $forceview = 1;
@@ -3156,7 +3436,7 @@ sub can_edit_resource {
                 } else {
                     $cfile = $env{'form.suppurl'};
                     my $escfile = &unescape($cfile);
-                    if ($escfile =~ m{^/adm/$cdom/$cnum/\d+/exttools?$}) {
+                    if ($escfile =~ m{^/adm/$cdom/$cnum/\d+/ext\.tool$}) {
                         $cfile = '/adm/wrapper'.$escfile;
                     } else {
                         $escfile =~ s{^http://}{};
@@ -3491,12 +3771,12 @@ sub userfileupload {
                          '_'.$env{'user.domain'}.'/pending';
         } elsif (($context eq 'existingfile') || ($context eq 'canceloverwrite')) {
             my ($docuname,$docudom);
-            if ($destudom) {
+            if ($destudom =~ /^$match_domain$/) {
                 $docudom = $destudom;
             } else {
                 $docudom = $env{'user.domain'};
             }
-            if ($destuname) {
+            if ($destuname =~ /^$match_username$/) {
                 $docuname = $destuname;
             } else {
                 $docuname = $env{'user.name'};
@@ -4043,7 +4323,7 @@ sub flushcourselogs {
         }
     }
 #
-# Reverse lookup of domain roles (dc, ad, li, sc, au)
+# Reverse lookup of domain roles (dc, ad, li, sc, dh, da, au)
 #
     my %domrolebuffer = ();
     foreach my $entry (keys(%domainrolehash)) {
@@ -4058,10 +4338,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);
 	    }
         }
@@ -4181,7 +4470,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|da)/ ) {
        my (undef,$rudom,$runame,$rsec)=split(/\//,$area);
        $domainrolehash
          {$trole.':'.$username.':'.$domain.':'.$runame.':'.$rudom.':'.$rsec}
@@ -4408,6 +4697,195 @@ sub get_my_roles {
     return %returnhash;
 }
 
+sub get_all_adhocroles {
+    my ($dom) = @_;
+    my @roles_by_num = ();
+    my %domdefaults = &get_domain_defaults($dom);
+    my (%description,%access_in_dom,%access_info);
+    if (ref($domdefaults{'adhocroles'}) eq 'HASH') {
+        my $count = 0;
+        my %domcurrent = %{$domdefaults{'adhocroles'}};
+        my %ordered;
+        foreach my $role (sort(keys(%domcurrent))) {
+            my ($order,$desc,$access_in_dom);
+            if (ref($domcurrent{$role}) eq 'HASH') {
+                $order = $domcurrent{$role}{'order'};
+                $desc = $domcurrent{$role}{'desc'};
+                $access_in_dom{$role} = $domcurrent{$role}{'access'};
+                $access_info{$role} = $domcurrent{$role}{$access_in_dom{$role}};
+            }
+            if ($order eq '') {
+                $order = $count;
+            }
+            $ordered{$order} = $role;
+            if ($desc ne '') {
+                $description{$role} = $desc;
+            } else {
+                $description{$role}= $role;
+            }
+            $count++;
+        }
+        foreach my $item (sort {$a <=> $b } (keys(%ordered))) {
+            push(@roles_by_num,$ordered{$item});
+        }
+    }
+    return (\@roles_by_num,\%description,\%access_in_dom,\%access_info);
+}
+
+sub get_my_adhocroles {
+    my ($cid,$checkreg) = @_;
+    my ($cdom,$cnum,%info,@possroles,$description,$roles_by_num);
+    if ($env{'request.course.id'} eq $cid) {
+        $cdom = $env{'course.'.$cid.'.domain'};
+        $cnum = $env{'course.'.$cid.'.num'};
+        $info{'internal.coursecode'} = $env{'course.'.$cid.'.internal.coursecode'};
+    } elsif ($cid =~ /^($match_domain)_($match_courseid)$/) {
+        $cdom = $1;
+        $cnum = $2;
+        %info = &Apache::lonnet::get('environment',['internal.coursecode'],
+                                     $cdom,$cnum);
+    }
+    if (($info{'internal.coursecode'} ne '') && ($checkreg)) {
+        my $user = $env{'user.name'}.':'.$env{'user.domain'};
+        my %rosterhash = &get('classlist',[$user],$cdom,$cnum);
+        if ($rosterhash{$user} ne '') {
+            my $type = (split(/:/,$rosterhash{$user}))[5];
+            return ([],{}) if ($type eq 'auto');
+        }
+    }
+    if (($cdom ne '') && ($cnum ne ''))  {
+        if (($env{"user.role.dh./$cdom/"}) || ($env{"user.role.da./$cdom/"})) {
+            my $then=$env{'user.login.time'};
+            my $update=$env{'user.update.time'};
+            if (!$update) {
+                $update = $then;
+            }
+            my @liveroles;
+            foreach my $role ('dh','da') {
+                if ($env{"user.role.$role./$cdom/"}) {
+                    my ($tstart,$tend)=split(/\./,$env{"user.role.$role./$cdom/"});
+                    my $limit = $update;
+                    if ($env{'request.role'} eq "$role./$cdom/") {
+                        $limit = $then;
+                    }
+                    my $activerole = 1;
+                    if ($tstart && $tstart>$limit) { $activerole = 0; }
+                    if ($tend   && $tend  <$limit) { $activerole = 0; }
+                    if ($activerole) {
+                        push(@liveroles,$role);
+                    }
+                }
+            }
+            if (@liveroles) {
+                if (&homeserver($cnum,$cdom) ne 'no_host') {
+                    my ($accessref,$accessinfo,%access_in_dom);
+                    ($roles_by_num,$description,$accessref,$accessinfo) = &get_all_adhocroles($cdom);
+                    if (ref($roles_by_num) eq 'ARRAY') {
+                        if (@{$roles_by_num}) {
+                            my %settings;
+                            if ($env{'request.course.id'} eq $cid) {
+                                foreach my $envkey (keys(%env)) {
+                                    if ($envkey =~ /^\Qcourse.$cid.\E(internal\.adhoc.+)$/) {
+                                        $settings{$1} = $env{$envkey};
+                                    }
+                                }
+                            } else {
+                                %settings = &dump('environment',$cdom,$cnum,'internal\.adhoc');
+                            }
+                            my %setincrs;
+                            if ($settings{'internal.adhocaccess'}) {
+                                map { $setincrs{$_} = 1; } split(/,/,$settings{'internal.adhocaccess'});
+                            }
+                            my @statuses;
+                            if ($env{'environment.inststatus'}) {
+                                @statuses = split(/,/,$env{'environment.inststatus'});
+                            }
+                            my $user = $env{'user.name'}.':'.$env{'user.domain'};
+                            if (ref($accessref) eq 'HASH') {
+                                %access_in_dom = %{$accessref};
+                            }
+                            foreach my $role (@{$roles_by_num}) {
+                                my ($curraccess,@okstatus,@personnel);
+                                if ($setincrs{$role}) {
+                                    ($curraccess,my $rest) = split(/=/,$settings{'internal.adhoc.'.$role});
+                                    if ($curraccess eq 'status') {
+                                        @okstatus = split(/\&/,$rest);
+                                    } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) {
+                                        @personnel = split(/\&/,$rest);
+                                    }
+                                } else {
+                                    $curraccess = $access_in_dom{$role};
+                                    if (ref($accessinfo) eq 'HASH') {
+                                        if ($curraccess eq 'status') {
+                                            if (ref($accessinfo->{$role}) eq 'ARRAY') {
+                                                @okstatus = @{$accessinfo->{$role}};
+                                            }
+                                        } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) {
+                                            if (ref($accessinfo->{$role}) eq 'ARRAY') {
+                                                @personnel = @{$accessinfo->{$role}};
+                                            }
+                                        }
+                                    }
+                                }
+                                if ($curraccess eq 'none') {
+                                    next;
+                                } elsif ($curraccess eq 'all') {
+                                    push(@possroles,$role);
+                                } elsif ($curraccess eq 'dh') {
+                                    if (grep(/^dh$/,@liveroles)) {
+                                        push(@possroles,$role);
+                                    } else {
+                                        next;
+                                    }
+                                } elsif ($curraccess eq 'da') {
+                                    if (grep(/^da$/,@liveroles)) {
+                                        push(@possroles,$role);
+                                    } else {
+                                        next;
+                                    }
+                                } elsif ($curraccess eq 'status') {
+                                    if (@okstatus) {
+                                        if (!@statuses) {
+                                            if (grep(/^default$/,@okstatus)) {
+                                                push(@possroles,$role);
+                                            }
+                                        } else {
+                                            foreach my $status (@okstatus) {
+                                                if (grep(/^\Q$status\E$/,@statuses)) {
+                                                    push(@possroles,$role);
+                                                    last;
+                                                }
+                                            }
+                                        }
+                                    }
+                                } elsif (($curraccess eq 'exc') || ($curraccess eq 'inc')) {
+                                    if (grep(/^\Q$user\E$/,@personnel)) {
+                                        if ($curraccess eq 'exc') {
+                                            push(@possroles,$role);
+                                        }
+                                    } elsif ($curraccess eq 'inc') {
+                                        push(@possroles,$role);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    unless (ref($description) eq 'HASH') {
+        if (ref($roles_by_num) eq 'ARRAY') {
+            my %desc;
+            map { $desc{$_} = $_; } (@{$roles_by_num});
+            $description = \%desc;
+        } else {
+            $description = {};
+        }
+    }
+    return (\@possroles,$description);
+}
+
 # ----------------------------------------------------- Frontpage Announcements
 #
 #
@@ -4648,6 +5126,21 @@ sub get_domain_roles {
     return %personnel;
 }
 
+sub get_active_domroles {
+    my ($dom,$roles) = @_;
+    return () unless (ref($roles) eq 'ARRAY');
+    my $now = time;
+    my %dompersonnel = &get_domain_roles($dom,$roles,$now,$now);
+    my %domroles;
+    foreach my $server (keys(%dompersonnel)) {
+        foreach my $user (sort(keys(%{$dompersonnel{$server}}))) {
+            my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,$user);
+            $domroles{$uname.':'.$udom} = $dompersonnel{$server}{$user};
+        }
+    }
+    return %domroles;
+}
+
 # ----------------------------------------------------------- Interval timing 
 
 {
@@ -5502,9 +5995,10 @@ sub rolesinit {
         }
     }
 
-    @userroles{'user.author', 'user.adv'} = &set_userprivs(\%userroles,
-        \%allroles, \%allgroups);
+    @userroles{'user.author','user.adv','user.rar'} = &set_userprivs(\%userroles,
+                                                          \%allroles, \%allgroups);
     $env{'user.adv'} = $userroles{'user.adv'};
+    $env{'user.rar'} = $userroles{'user.rar'};
 
     return (\%userroles,\%firstaccenv,\%timerintenv);
 }
@@ -5540,6 +6034,10 @@ sub custom_roleprivs {
                     $$allroles{$spec.'./'.$tdomain.'/'}.=':'.$dompriv;
                 }
                 if (($trest ne '') && (defined($coursepriv))) {
+                    if ($trole =~ m{^cr/$tdomain/$tdomain\Q-domainconfig\E/([^/]+)$}) {
+                        my $rolename = $1;
+                        $coursepriv = &course_adhocrole_privs($rolename,$tdomain,$trest,$coursepriv);
+                    }
                     $$allroles{'cm.'.$area}.=':'.$coursepriv;
                     $$allroles{$spec.'.'.$area}.=':'.$coursepriv;
                 }
@@ -5548,6 +6046,48 @@ sub custom_roleprivs {
     }
 }
 
+sub course_adhocrole_privs {
+    my ($rolename,$cdom,$cnum,$coursepriv) = @_;
+    my %overrides = &get('environment',["internal.adhocpriv.$rolename"],$cdom,$cnum);
+    if ($overrides{"internal.adhocpriv.$rolename"}) {
+        my (%currprivs,%storeprivs);
+        foreach my $item (split(/:/,$coursepriv)) {
+            my ($priv,$restrict) = split(/\&/,$item);
+            $currprivs{$priv} = $restrict;
+        }
+        my (%possadd,%possremove,%full);
+        foreach my $item (split(/\:/,$Apache::lonnet::pr{'cr:c'})) {
+            my ($priv,$restrict)=split(/\&/,$item);
+            $full{$priv} = $restrict;
+        }
+        foreach my $item (split(/,/,$overrides{"internal.adhocpriv.$rolename"})) {
+             next if ($item eq '');
+             my ($rule,$rest) = split(/=/,$item);
+             next unless (($rule eq 'off') || ($rule eq 'on'));
+             foreach my $priv (split(/:/,$rest)) {
+                 if ($priv ne '') {
+                     if ($rule eq 'off') {
+                         $possremove{$priv} = 1;
+                     } else {
+                         $possadd{$priv} = 1;
+                     }
+                 }
+             }
+         }
+         foreach my $priv (sort(keys(%full))) {
+             if (exists($currprivs{$priv})) {
+                 unless (exists($possremove{$priv})) {
+                     $storeprivs{$priv} = $currprivs{$priv};
+                 }
+             } elsif (exists($possadd{$priv})) {
+                 $storeprivs{$priv} = $full{$priv};
+             }
+         }
+         $coursepriv = ':'.join(':',map { $_.'&'.$storeprivs{$_}; } sort(keys(%storeprivs)));
+     }
+     return $coursepriv;
+}
+
 sub group_roleprivs {
     my ($allgroups,$area,$group_privs,$tend,$tstart) = @_;
     my $access = 1;
@@ -5582,6 +6122,7 @@ sub set_userprivs {
     my ($userroles,$allroles,$allgroups,$groups_roles) = @_; 
     my $author=0;
     my $adv=0;
+    my $rar=0;
     my %grouproles = ();
     if (keys(%{$allgroups}) > 0) {
         my @groupkeys; 
@@ -5629,6 +6170,7 @@ sub set_userprivs {
                     $thesepriv{$privilege}.=$restrictions;
                 }
                 if ($thesepriv{'adv'} eq 'F') { $adv=1; }
+                if ($thesepriv{'rar'} eq 'F') { $rar=1; }
             }
         }
         my $thesestr='';
@@ -5637,7 +6179,7 @@ sub set_userprivs {
 	}
         $userroles->{'user.priv.'.$role} = $thesestr;
     }
-    return ($author,$adv);
+    return ($author,$adv,$rar);
 }
 
 sub role_status {
@@ -5682,9 +6224,10 @@ sub role_status {
                                 push(@rolecodes,$$role);
                                 &standard_roleprivs(\%allroles,$$role,$tdomain,$spec,$trest,$$where);
                             }
-                            my ($author,$adv)= &set_userprivs(\%userroles,\%allroles,\%allgroups,\%groups_roles);
+                            my ($author,$adv,$rar)= &set_userprivs(\%userroles,\%allroles,\%allgroups,
+                                                                   \%groups_roles);
                             &appenv(\%userroles,\@rolecodes);
-                            &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role);
+                            &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$spec);
                         }
                     }
                     $$tstatus = 'is';
@@ -5760,39 +6303,56 @@ sub delete_env_groupprivs {
 }
 
 sub check_adhoc_privs {
-    my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller) = @_;
+    my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller,$sec) = @_;
     my $cckey = 'user.role.'.$checkrole.'./'.$cdom.'/'.$cnum;
+    if ($sec) {
+        $cckey .= '/'.$sec;
+    } 
     my $setprivs;
     if ($env{$cckey}) {
         my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend);
         &role_status($cckey,$update,$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,$caller,$sec);
             $setprivs = 1;
         }
     } else {
-        &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller);
+        &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller,$sec);
         $setprivs = 1;
     }
     return $setprivs;
 }
 
 sub set_adhoc_privileges {
-# role can be cc or ca
-    my ($dcdom,$pickedcourse,$role,$caller) = @_;
+# role can be cc, ca, or cr/<dom>/<dom>-domainconfig/role
+    my ($dcdom,$pickedcourse,$role,$caller,$sec) = @_;
     my $area = '/'.$dcdom.'/'.$pickedcourse;
+    if ($sec ne '') {
+        $area .= '/'.$sec;
+    }
     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{^\Qcr/$dcdom/$dcdom\E\-domainconfig/(\w+)$}) {
+        my $rolename = $1;
+        &custom_roleprivs(\%rolehash,$role,$dcdom,$pickedcourse,$spec,$area);
+        my %domdef = &get_domain_defaults($dcdom);
+        if (ref($domdef{'adhocroles'}) eq 'HASH') {
+            if (ref($domdef{'adhocroles'}{$rolename}) eq 'HASH') {
+                &appenv({'request.role.desc' => $domdef{'adhocroles'}{$rolename}{'desc'},});
+            }
+        }
+    } else {
+        &standard_roleprivs(\%rolehash,$role,$dcdom,$spec,$pickedcourse,$area);
+    }
+    my ($author,$adv,$rar)= &set_userprivs(\%userroles,\%rolehash);
     &appenv(\%userroles,[$role,'cm']);
-    &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role);
+    &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$spec);
     unless ($caller eq 'constructaccess' && $env{'request.course.id'}) {
         &appenv( {'request.role'        => $spec,
                   'request.role.domain' => $dcdom,
-                  'request.course.sec'  => ''
+                  'request.course.sec'  => $sec,
                  }
                );
         my $tadv=0;
@@ -5942,7 +6502,7 @@ sub currentdump {
    #
    my %returnhash=();
    #
-   if ($rep eq "unknown_cmd") { 
+   if ($rep eq 'unknown_cmd') {
        # an old lond will not know currentdump
        # Do a dump and make it look like a currentdump
        my @tmp = &dumpstore($courseid,$sdom,$sname,'.');
@@ -6176,9 +6736,11 @@ sub tmpget {
     if (!defined($server)) { $server = $perlvar{'lonHostID'}; }
     my $rep=&reply("tmpget:$token",$server);
     my %returnhash;
+    if ($rep =~ /^(con_lost|error|no_such_host)/i) {
+        return %returnhash;
+    }
     foreach my $item (split(/\&/,$rep)) {
 	my ($key,$value)=split(/=/,$item);
-        next if ($key =~ /^error: 2 /);
 	$returnhash{&unescape($key)}=&thaw_unescape($value);
     }
     return %returnhash;
@@ -6874,7 +7436,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|ext\.tool)$})) 
 	 || (($uri=~/\.meta$/) && ($uri!~m|^uploaded/|) )) 
 	&& ($priv eq 'bre')) {
 	return 'F';
@@ -7362,7 +7924,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 ''; }
@@ -7383,6 +7945,15 @@ sub constructaccess {
             $ownerhome = &homeserver($ownername,$ownerdomain);
             return ($ownername,$ownerdomain,$ownerhome);
         }
+        if ($env{'request.course.id'}) {
+            if (($ownername eq $env{'course.'.$env{'request.course.id'}.'.num'}) &&
+                ($ownerdomain eq $env{'course.'.$env{'request.course.id'}.'.domain'})) {
+                if (&allowed('mdc',$env{'request.course.id'})) {
+                    $ownerhome = $env{'course.'.$env{'request.course.id'}.'.home'};
+                    return ($ownername,$ownerdomain,$ownerhome);
+                }
+            }
+        }
     }
 
 # We don't have any access right now. If we are not possibly going to do anything about this,
@@ -7701,7 +8272,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"; }
@@ -7729,11 +8300,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';
   }
@@ -7815,10 +8394,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);
@@ -7836,17 +8417,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;
@@ -7881,11 +8462,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>);
@@ -8774,7 +9361,8 @@ 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') ||
+                 ($role eq 'da')) {
             &domainrolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag,
                            $context);
         } elsif (($role eq 'ca') || ($role eq 'aa')) {
@@ -9079,7 +9667,7 @@ sub modifyuser {
 sub modifystudent {
     my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec,
         $end,$start,$forceid,$desiredhome,$email,$type,$locktype,$cid,
-        $selfenroll,$context,$inststatus,$credits)=@_;
+        $selfenroll,$context,$inststatus,$credits,$instsec)=@_;
     if (!$cid) {
 	unless ($cid=$env{'request.course.id'}) {
 	    return 'not_in_class';
@@ -9095,13 +9683,13 @@ sub modifystudent {
     $uid = undef if (!$forceid);
     $reply = &modify_student_enrollment($udom,$uname,$uid,$first,$middle,$last,
                                         $gene,$usec,$end,$start,$type,$locktype,
-                                        $cid,$selfenroll,$context,$credits);
+                                        $cid,$selfenroll,$context,$credits,$instsec);
     return $reply;
 }
 
 sub modify_student_enrollment {
     my ($udom,$uname,$uid,$first,$middle,$last,$gene,$usec,$end,$start,$type,
-        $locktype,$cid,$selfenroll,$context,$credits) = @_;
+        $locktype,$cid,$selfenroll,$context,$credits,$instsec) = @_;
     my ($cdom,$cnum,$chome);
     if (!$cid) {
 	unless ($cid=$env{'request.course.id'}) {
@@ -9148,7 +9736,7 @@ sub modify_student_enrollment {
     my %old_entry = &Apache::lonnet::get('classlist',[$user],$cdom,$cnum);
     my $reply=cput('classlist',
 		   {$user => 
-			join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits) },
+			join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits,$instsec) },
 		   $cdom,$cnum);
     if (($reply eq 'ok') || ($reply eq 'delayed')) {
         &devalidate_getsection_cache($udom,$uname,$cid);
@@ -10134,7 +10722,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');
         }
@@ -10207,6 +10812,115 @@ sub stat_file {
     return ();
 }
 
+# --------------------------------------------------------- recursedirs
+# Recursive function to traverse either a specific user's Authoring Space
+# or corresponding Published Resource Space, and populate the hash ref:
+# $dirhashref with URLs of all directories, and if $filehashref hash
+# ref arg is provided, the URLs of any files, excluding versioned, .meta,
+# or .rights files in resource space, and .meta, .save, .log, and .bak
+# files in Authoring Space.
+#
+# Inputs:
+#
+# $is_home - true if current server is home server for user's space
+# $context - either: priv, or res respectively for Authoring or Resource Space.
+# $docroot - Document root (i.e., /home/httpd/html
+# $toppath - Top level directory (i.e., /res/$dom/$uname or /priv/$dom/$uname
+# $relpath - Current path (relative to top level).
+# $dirhashref - reference to hash to populate with URLs of directories (Required)
+# $filehashref - reference to hash to populate with URLs of files (Optional)
+#
+# Returns: nothing
+#
+# Side Effects: populates $dirhashref, and $filehashref (if provided).
+#
+# Currently used by interface/londocs.pm to create linked select boxes for
+# directory and filename to import a Course "Author" resource into a course, and
+# also to create linked select boxes for Authoring Space and Directory to choose
+# save location for creation of a new "standard" problem from the Course Editor.
+#
+
+sub recursedirs {
+    my ($is_home,$context,$docroot,$toppath,$relpath,$dirhashref,$filehashref) = @_;
+    return unless (ref($dirhashref) eq 'HASH');
+    my $currpath = $docroot.$toppath;
+    if ($relpath) {
+        $currpath .= "/$relpath";
+    }
+    my $savefile;
+    if (ref($filehashref)) {
+        $savefile = 1;
+    }
+    if ($is_home) {
+        if (opendir(my $dirh,$currpath)) {
+            foreach my $item (sort { lc($a) cmp lc($b) } grep(!/^\.+$/,readdir($dirh))) {
+                next if ($item eq '');
+                if (-d "$currpath/$item") {
+                    my $newpath;
+                    if ($relpath) {
+                        $newpath = "$relpath/$item";
+                    } else {
+                        $newpath = $item;
+                    }
+                    $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1;
+                    &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref);
+                } elsif ($savefile) {
+                    if ($context eq 'priv') {
+                        unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) {
+                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
+                        }
+                    } else {
+                        unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/) || ($item =~ /\.rights$/)) {
+                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
+                        }
+                    }
+                }
+            }
+            closedir($dirh);
+        }
+    } else {
+        my ($dirlistref,$listerror) =
+            &dirlist($toppath.$relpath);
+        my @dir_lines;
+        my $dirptr=16384;
+        if (ref($dirlistref) eq 'ARRAY') {
+            foreach my $dir_line (sort
+                              {
+                                  my ($afile)=split('&',$a,2);
+                                  my ($bfile)=split('&',$b,2);
+                                  return (lc($afile) cmp lc($bfile));
+                              } (@{$dirlistref})) {
+                my ($item,$dom,undef,$testdir,undef,undef,undef,undef,$size,undef,$mtime,undef,undef,undef,$obs,undef) =
+                    split(/\&/,$dir_line,16);
+                $item =~ s/\s+$//;
+                next if (($item =~ /^\.\.?$/) || ($obs));
+                if ($dirptr&$testdir) {
+                    my $newpath;
+                    if ($relpath) {
+                        $newpath = "$relpath/$item";
+                    } else {
+                        $relpath = '/';
+                        $newpath = $item;
+                    }
+                    $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1;
+                    &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref);
+                } elsif ($savefile) {
+                    if ($context eq 'priv') {
+                        unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) {
+                            $filehashref->{$relpath}{$item} = 1;
+                        }
+                    } else {
+                        unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/)) {
+                            $filehashref->{$relpath}{$item} = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+
 # -------------------------------------------------------- Value of a Condition
 
 # gets the value of a specific preevaluated condition
@@ -10430,6 +11144,16 @@ sub get_domain_ltitools {
         my %domconfig = &get_dom('configuration',['ltitools'],$cdom);
         if (ref($domconfig{'ltitools'}) eq 'HASH') {
             %ltitools = %{$domconfig{'ltitools'}};
+            my %encdomconfig = &get_dom('encconfig',['ltitools'],$cdom);
+            if (ref($encdomconfig{'ltitools'}) eq 'HASH') {
+                foreach my $id (keys(%ltitools)) {
+                    if (ref($encdomconfig{'ltitools'}{$id}) eq 'HASH') {
+                        foreach my $item ('key','secret') {
+                            $ltitools{$id}{$item} = $encdomconfig{'ltitools'}{$id}{$item};
+                        }
+                    }
+                }
+            }
         }
         my $cachetime = 24*60*60;
         &do_cache_new('ltitools',$cdom,\%ltitools,$cachetime);
@@ -10939,7 +11663,7 @@ sub metadata {
     # if it is a non metadata possible uri return quickly
     if (($uri eq '') || 
 	(($uri =~ m|^/*adm/|) && 
-	     ($uri !~ m|^adm/includes|) && ($uri !~ m{/(smppg|bulletinboard|exttools?)$})) ||
+	     ($uri !~ m|^adm/includes|) && ($uri !~ m{/(smppg|bulletinboard|ext\.tool)$})) ||
         ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ m{^/*uploaded/.+\.sequence$})) {
 	return undef;
     }
@@ -12215,7 +12939,6 @@ sub repcopy_userfile {
     }
 # now the path exists for sure
 # get a user agent
-    my $ua=new LWP::UserAgent;
     my $transferfile=$file.'.in.transfer';
 # FIXME: this should flock
     if (-e $transferfile) { return 'ok'; }
@@ -12225,7 +12948,7 @@ sub repcopy_userfile {
     my $protocol = $protocol{$homeserver};
     $protocol = 'http' if ($protocol ne 'https');
     $request=new HTTP::Request('GET',$protocol.'://'.&hostname($homeserver).'/raw/'.$uri);
-    my $response=$ua->request($request,$transferfile);
+    my $response = &LONCAPA::LWPReq::makerequest($homeserver,$request,$transferfile,\%perlvar,'',0,1);
 # did it work?
     if ($response->is_error()) {
 	unlink($transferfile);
@@ -12269,9 +12992,8 @@ sub getuploaded {
     my $protocol = $protocol{$homeserver};
     $protocol = 'http' if ($protocol ne 'https');
     $uri = $protocol.'://'.&hostname($homeserver).'/raw/'.$uri;
-    my $ua=new LWP::UserAgent;
     my $request=new HTTP::Request($reqtype,$uri);
-    my $response=$ua->request($request);
+    my $response=&LONCAPA::LWPReq::makerequest($homeserver,$request,'',\%perlvar,'',0,1);
     $$rtncode = $response->code;
     if (! $response->is_success()) {
 	return 'failed';
@@ -12472,7 +13194,7 @@ sub clutter {
 #		&logthis("Got a blank emb style");
 	    }
 	}
-    } elsif ($thisfn =~ m{^/adm/$match_domain/$match_courseid/\d+/exttools?$}) {
+    } elsif ($thisfn =~ m{^/adm/$match_domain/$match_courseid/\d+/ext\.tool$}) {
         $thisfn='/adm/wrapper'.$thisfn;
     }
     return $thisfn;
@@ -12559,10 +13281,8 @@ sub get_dns {
     }
     while (%alldns) {
 	my ($dns) = sort { $b cmp $a } keys(%alldns);
-	my $ua=new LWP::UserAgent;
-        $ua->timeout(30);
 	my $request=new HTTP::Request('GET',"$alldns{$dns}://$dns$url");
-	my $response=$ua->request($request);
+        my $response = &LONCAPA::LWPReq::makerequest('',$request,'',\%perlvar,30,0);
         delete($alldns{$dns});
 	next if ($response->is_error());
 	my @content = split("\n",$response->content);
@@ -12720,8 +13440,23 @@ sub fetch_dns_checksums {
 	    my ($id,$domain,$role,$name,$protocol,$intdom)=split(/:/,$configline);
 	    $name=~s/\s//g;
 	    if ($id && $domain && $role && $name) {
+                if ((exists($hostname{$id})) && ($hostname{$id} ne '')) {
+                    my $curr = $hostname{$id};
+                    my $skip;
+                    if (ref($name_to_host{$curr}) eq 'ARRAY') {
+                        if (($curr eq $name) && (@{$name_to_host{$curr}} == 1)) {
+                            $skip = 1;
+                        } else {
+                            @{$name_to_host{$curr}} = grep { $_ ne $id } @{$name_to_host{$curr}};
+                        }
+                    }
+                    unless ($skip) {
+                        push(@{$name_to_host{$name}},$id);
+                    }
+                } else {
+                    push(@{$name_to_host{$name}},$id);
+                }
 		$hostname{$id}=$name;
-		push(@{$name_to_host{$name}}, $id);
 		$hostdom{$id}=$domain;
 		if ($role eq 'library') { $libserv{$id}=$name; }
                 if (defined($protocol)) {
@@ -12744,6 +13479,7 @@ sub fetch_dns_checksums {
 	&purge_remembered();
 	&reset_domain_info();
 	&reset_hosts_ip_info();
+        undef(%internetdom);
 	undef(%name_to_host);
 	undef(%hostname);
 	undef(%hostdom);
@@ -12786,6 +13522,11 @@ sub fetch_dns_checksums {
         return %hostdom;
     }
 
+    sub all_host_intdom {
+        &load_hosts_tab() if (!$loaded);
+        return %internetdom;
+    }
+
     sub is_library {
 	&load_hosts_tab() if (!$loaded);
 
@@ -13539,9 +14280,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 *
 
@@ -13745,6 +14487,8 @@ Inputs:
 
 =item $credits, number of credits student will earn from this class
 
+=item $instsec, institutional course section code for student
+
 =back