--- loncom/lonnet/perl/lonnet.pm	2015/05/22 17:58:00	1.1172.2.67
+++ loncom/lonnet/perl/lonnet.pm	2018/04/06 18:20:30	1.1172.2.93.4.9
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1172.2.67 2015/05/22 17:58:00 raeburn Exp $
+# $Id: lonnet.pm,v 1.1172.2.93.4.9 2018/04/06 18:20:30 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -89,7 +89,7 @@ use GDBM_File;
 use HTML::LCParser;
 use Fcntl qw(:flock);
 use Storable qw(thaw nfreeze);
-use Time::HiRes qw( gettimeofday tv_interval );
+use Time::HiRes qw( sleep gettimeofday tv_interval );
 use Cache::Memcached;
 use Digest::MD5;
 use Math::Random;
@@ -102,7 +102,7 @@ use LONCAPA::Lond;
 use File::Copy;
 
 my $readit;
-my $max_connection_retries = 10;     # Or some such value.
+my $max_connection_retries = 20;     # Or some such value.
 
 require Exporter;
 
@@ -142,7 +142,7 @@ our @EXPORT = qw(%env);
 sub logtouch {
     my $execdir=$perlvar{'lonDaemons'};
     unless (-e "$execdir/logs/lonnet.log") {	
-	open(my $fh,">>$execdir/logs/lonnet.log");
+	open(my $fh,">>","$execdir/logs/lonnet.log");
 	close $fh;
     }
     my ($wwwuid,$wwwgid)=(getpwnam('www'))[2,3];
@@ -154,7 +154,7 @@ sub logthis {
     my $execdir=$perlvar{'lonDaemons'};
     my $now=time;
     my $local=localtime($now);
-    if (open(my $fh,">>$execdir/logs/lonnet.log")) {
+    if (open(my $fh,">>","$execdir/logs/lonnet.log")) {
 	my $logstring = $local. " ($$): ".$message."\n"; # Keep any \'s in string.
 	print $fh $logstring;
 	close($fh);
@@ -167,7 +167,7 @@ sub logperm {
     my $execdir=$perlvar{'lonDaemons'};
     my $now=time;
     my $local=localtime($now);
-    if (open(my $fh,">>$execdir/logs/lonnet.perm.log")) {
+    if (open(my $fh,">>","$execdir/logs/lonnet.perm.log")) {
 	print $fh "$now:$message:$local\n";
 	close($fh);
     }
@@ -370,7 +370,7 @@ sub subreply {
 
     my $lockfile=$peerfile.".lock";
     while (-e $lockfile) {	# Need to wait for the lockfile to disappear.
-	sleep(1);
+	sleep(0.1);
     }
     # At this point, either a loncnew parent is listening or an old lonc
     # or loncnew child is listening so we can connect or everything's dead.
@@ -388,7 +388,7 @@ sub subreply {
 	} else {
 	    &create_connection(&hostname($server),$server);
 	}
-        sleep(1);		# Try again later if failed connection.
+        sleep(0.1);		# Try again later if failed connection.
     }
     my $answer;
     if ($client) {
@@ -417,8 +417,8 @@ sub reply {
 
 sub reconlonc {
     my ($lonid) = @_;
-    my $hostname = &hostname($lonid);
     if ($lonid) {
+        my $hostname = &hostname($lonid);
 	my $peerfile="$perlvar{'lonSockDir'}/$hostname";
 	if ($hostname && -e $peerfile) {
 	    &logthis("Trying to reconnect lonc for $lonid ($hostname)");
@@ -436,7 +436,7 @@ sub reconlonc {
 
     &logthis("Trying to reconnect lonc");
     my $loncfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
-    if (open(my $fh,"<$loncfile")) {
+    if (open(my $fh,"<",$loncfile)) {
 	my $loncpid=<$fh>;
         chomp($loncpid);
         if (kill 0 => $loncpid) {
@@ -464,7 +464,7 @@ sub critical {
     }
     my $answer=reply($cmd,$server);
     if ($answer eq 'con_lost') {
-	&reconlonc("$perlvar{'lonSockDir'}/$server");
+	&reconlonc($server);
 	my $answer=reply($cmd,$server);
         if ($answer eq 'con_lost') {
             my $now=time;
@@ -476,16 +476,16 @@ sub critical {
             $dumpcount++;
             {
 		my $dfh;
-		if (open($dfh,">$dfilename")) {
+		if (open($dfh,">",$dfilename)) {
 		    print $dfh "$cmd\n"; 
 		    close($dfh);
 		}
             }
-            sleep 2;
+            sleep 1;
             my $wcmd='';
             {
 		my $dfh;
-		if (open($dfh,"<$dfilename")) {
+		if (open($dfh,"<",$dfilename)) {
 		    $wcmd=<$dfh>; 
 		    close($dfh);
 		}
@@ -601,7 +601,7 @@ 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'));
     if ($name eq '') {
         $name = 'lonID';
@@ -616,7 +616,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);
@@ -1002,7 +1011,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
@@ -1274,7 +1283,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'};
@@ -1283,7 +1292,7 @@ sub check_loadbalancing {
     my $uintdom = &Apache::lonnet::internet_dom($uprimary_id);
     my $intdom = &Apache::lonnet::internet_dom($lonhost);
     my $serverhomedom = &host_domain($lonhost);
-
+    my $domneedscache; 
     my $cachetime = 60*60*24;
 
     if (($uintdom ne '') && ($uintdom eq $intdom)) {
@@ -1298,6 +1307,8 @@ sub check_loadbalancing {
             &Apache::lonnet::get_dom('configuration',['loadbalancing'],$dom_in_use);
         if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
             $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime);
+        } else {
+            $domneedscache = $dom_in_use;
         }
     }
     if (ref($result) eq 'HASH') {
@@ -1356,7 +1367,9 @@ sub check_loadbalancing {
             my %domconfig =
                 &Apache::lonnet::get_dom('configuration',['loadbalancing'],$serverhomedom);
             if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
-                $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime);
+                $result = &do_cache_new('loadbalancing',$serverhomedom,$domconfig{'loadbalancing'},$cachetime);
+            } else {
+                $domneedscache = $serverhomedom;
             }
         }
         if (ref($result) eq 'HASH') {
@@ -1376,12 +1389,21 @@ sub check_loadbalancing {
                 $is_balancer = 1;
                 $offloadto = &this_host_spares($dom_in_use);
             }
+            unless (defined($cached)) {
+                $domneedscache = $serverhomedom;
+            }
         }
     } else {
         if ($perlvar{'lonBalancer'} eq 'yes') {
             $is_balancer = 1;
             $offloadto = &this_host_spares($dom_in_use);
         }
+        unless (defined($cached)) {
+            $domneedscache = $serverhomedom;
+        }
+    }
+    if ($domneedscache) {
+        &do_cache_new('loadbalancing',$domneedscache,$is_balancer,$cachetime);
     }
     if ($is_balancer) {
         my $lowest_load = 30000;
@@ -1412,13 +1434,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});
+                    }
                 }
             }
         }
@@ -1554,7 +1578,7 @@ sub idget {
     
     my %servers = &get_servers($udom,'library');
     foreach my $tryserver (keys(%servers)) {
-	my $idlist=join('&',@ids);
+	my $idlist=join('&', map { &escape($_); } @ids);
 	$idlist=~tr/A-Z/a-z/; 
 	my $reply=&reply("idget:$udom:".$idlist,$tryserver);
 	my @answer=();
@@ -1564,7 +1588,7 @@ sub idget {
 	my $i;
 	for ($i=0;$i<=$#ids;$i++) {
 	    if ($answer[$i]) {
-		$returnhash{$ids[$i]}=$answer[$i];
+		$returnhash{$ids[$i]}=&unescape($answer[$i]);
 	    } 
 	}
     } 
@@ -1673,7 +1697,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;
@@ -1717,7 +1746,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");
     }
@@ -1793,7 +1826,7 @@ sub retrieve_inst_usertypes {
 
 sub is_domainimage {
     my ($url) = @_;
-    if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+-) {
+    if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+[^/]-) {
         if (&domain($1) ne '') {
             return '1';
         }
@@ -1808,13 +1841,24 @@ sub inst_directory_query {
     my $homeserver = &domain($udom,'primary');
     my $outcome;
     if ($homeserver ne '') {
+        unless ($homeserver eq $perlvar{'lonHostID'}) {
+            if ($srch->{'srchby'} eq 'email') {
+                my $lcrev = &get_server_loncaparev(undef,$homeserver);
+                my ($major,$minor,$subver) = ($lcrev =~ /^\'?(\d+)\.(\d+)\.([\w.\-]+)\'?$/);
+                if (($major eq '' && $minor eq '') || ($major < 2) ||
+                    (($major == 2) && ($minor < 11)) || 
+                    (($major == 2) && ($minor == 11) && ($subver !~ /^2\.B/))) {
+                    return;
+                }
+            }
+        }
 	my $queryid=&reply("querysend:instdirsearch:".
 			   &escape($srch->{'srchby'}).':'.
 			   &escape($srch->{'srchterm'}).':'.
 			   &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);
@@ -1849,6 +1893,15 @@ sub usersearch {
     my $query = 'usersearch';
     foreach my $tryserver (keys(%libserv)) {
         if (&host_domain($tryserver) eq $dom) {
+            unless ($tryserver eq $perlvar{'lonHostID'}) {
+                if ($srch->{'srchby'} eq 'email') {
+                    my $lcrev = &get_server_loncaparev(undef,$tryserver);
+                    my ($major,$minor,$subver) = ($lcrev =~ /^\'?(\d+)\.(\d+)\.([\w.\-]+)\'?$/);
+                    next if (($major eq '' && $minor eq '') || ($major < 2) ||
+                             (($major == 2) && ($minor < 11)) || 
+                             (($major == 2) && ($minor == 11) && ($subver !~ /^2\.B/)));
+                }
+            }
             my $host=&hostname($tryserver);
             my $queryid=
                 &reply("querysend:".&escape($query).':'.
@@ -1934,6 +1987,63 @@ sub get_instuser {
     return ($outcome,%userinfo);
 }
 
+sub get_multiple_instusers {
+    my ($udom,$users,$caller) = @_;
+    my ($outcome,$results);
+    if (ref($users) eq 'HASH') {
+        my $count = keys(%{$users});
+        my $requested = &freeze_escape($users);
+        my $homeserver = &domain($udom,'primary');
+        if ($homeserver ne '') {
+            my $queryid=&reply('querysend:getmultinstusers:::'.$caller.'='.$requested,$homeserver);
+            my $host=&hostname($homeserver);
+            if ($queryid !~/^\Q$host\E\_/) {
+                &logthis('get_multiple_instusers invalid queryid: '.$queryid.
+                         ' for host: '.$homeserver.'in domain '.$udom);
+                return ($outcome,$results);
+            }
+            my $response = &get_query_reply($queryid);
+            my $maxtries = 5;
+            if ($count > 100) {
+                $maxtries = 1+int($count/20);
+            }
+            my $tries = 1;
+            while (($response=~/^timeout/) && ($tries <= $maxtries)) {
+                $response = &get_query_reply($queryid);
+                $tries ++;
+            }
+            if ($response eq '') {
+                $results = {};
+                foreach my $key (keys(%{$users})) {
+                    my ($uname,$id);
+                    if ($caller eq 'id') {
+                        $id = $key;
+                    } else {
+                        $uname = $key;
+                    }
+                    my ($resp,%info) = &get_instuser($udom,$uname,$id);
+                    $outcome = $resp;
+                    if ($resp eq 'ok') {
+                        %{$results} = (%{$results}, %info);
+                    } else {
+                        last;
+                    }
+                }
+            } elsif(!&error($response) && ($response ne 'refused')) {
+                if (($response eq 'unavailable') || ($response eq 'invalid') || ($response eq 'timeout')) {
+                    $outcome = $response;
+                } else {
+                    ($outcome,my $userdata) = split(/=/,$response,2);
+                    if ($outcome eq 'ok') {
+                        $results = &thaw_unescape($userdata);
+                    }
+                }
+            }
+        }
+    }
+    return ($outcome,$results);
+}
+
 sub inst_rulecheck {
     my ($udom,$uname,$id,$item,$rules) = @_;
     my %returnhash;
@@ -2030,7 +2140,8 @@ sub get_domain_defaults {
                                   'requestcourses','inststatus',
                                   'coursedefaults','usersessions',
                                   'requestauthor','selfenrollment',
-                                  'coursecategories'],$domain);
+                                  'coursecategories','autoenroll',
+                                  'helpsettings'],$domain);
     my @coursetypes = ('official','unofficial','community','textbook');
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
@@ -2039,6 +2150,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');
@@ -2155,10 +2269,35 @@ sub get_domain_defaults {
             $domdefaults{'catunauth'} = $domconfig{'coursecategories'}{'unauth'};
         }
     }
+    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;
 }
 
+sub course_portal_url {
+    my ($cnum,$cdom) = @_;
+    my $chome = &homeserver($cnum,$cdom);
+    my $hostname = &hostname($chome);
+    my $protocol = $protocol{$chome};
+    $protocol = 'http' if ($protocol ne 'https');
+    my %domdefaults = &get_domain_defaults($cdom);
+    my $firsturl;
+    if ($domdefaults{'portal_def'}) {
+        $firsturl = $domdefaults{'portal_def'};
+    } else {
+        $firsturl = $protocol.'://'.$hostname;
+    }
+    return $firsturl;
+}
+
 # --------------------------------------------------- Assign a key to a student
 
 sub assign_access_key {
@@ -2395,21 +2534,25 @@ 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) for 
+                                     # keys in %remembered hash, which persists for
+                                     # duration of request (no restriction on key length).
+    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"); }
@@ -2419,13 +2562,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)) {
@@ -2441,17 +2585,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;
@@ -2914,6 +3058,14 @@ sub can_edit_resource {
                         $forceedit = 1;
                     }
                     $cfile = $resurl;
+                } elsif ($resurl =~ m{^/adm/wrapper/adm/$cdom/$cnum/\d+/ext\.tool$}) {
+                    $incourse = 1;
+                    if ($env{'form.forceedit'}) {
+                        $forceview = 1;
+                    } else {
+                        $forceedit = 1;
+                    }
+                    $cfile = $resurl;
                 } elsif ($resurl =~ m{^/?adm/viewclasslist$}) {
                     $incourse = 1;
                     if ($env{'form.forceedit'}) {
@@ -2938,6 +3090,14 @@ sub can_edit_resource {
                         $forceedit = 1;
                     }
                     $cfile = $resurl;
+            } elsif (($resurl =~ m{^/adm/wrapper/adm/$cdom/$cnum/\d+/ext\.tool$}) && ($env{'form.folderpath'} =~ /^supplemental/)) {
+                $incourse = 1;
+                if ($env{'form.forceedit'}) {
+                    $forceview = 1;
+                } else {
+                    $forceedit = 1;
+                }
+                $cfile = $resurl;
             } elsif (($resurl eq '/adm/extresedit') && ($symb || $env{'form.folderpath'})) {
                 $incourse = 1;
                 $forceview = 1;
@@ -2947,8 +3107,13 @@ sub can_edit_resource {
                     $cfile = &clutter($res);
                 } else {
                     $cfile = $env{'form.suppurl'};
-                    $cfile =~ s{^http://}{};
-                    $cfile = '/adm/wrapper/ext/'.$cfile;
+                    my $escfile = &unescape($cfile);
+                    if ($escfile =~ m{^/adm/$cdom/$cnum/\d+/ext\.tool$}) {
+                        $cfile = '/adm/wrapper'.$escfile;
+                    } else {
+                        $escfile =~ s{^http://}{};
+                        $cfile = &escape("/adm/wrapper/ext/$escfile");
+                    }
                 }
             } elsif ($resurl =~ m{^/?adm/viewclasslist$}) {
                 if ($env{'form.forceedit'}) {
@@ -3104,7 +3269,7 @@ sub process_coursefile {
                                  $home);
             }
         } elsif ($action eq 'uploaddoc') {
-            open(my $fh,'>'.$filepath.'/'.$fname);
+            open(my $fh,'>',$filepath.'/'.$fname);
             print $fh $env{'form.'.$source};
             close($fh);
             if ($parser eq 'parse') {
@@ -3162,7 +3327,7 @@ sub store_edited_file {
     ($fpath,$fname) = ($file =~ m|^(.*)/([^/]+)$|);
     $fpath=$docudom.'/'.$docuname.'/'.$fpath;
     my $filepath = &build_filepath($fpath);
-    open(my $fh,'>'.$filepath.'/'.$fname);
+    open(my $fh,'>',$filepath.'/'.$fname);
     print $fh $content;
     close($fh);
     my $home=&homeserver($docuname,$docudom);
@@ -3278,12 +3443,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'};
@@ -3313,7 +3478,7 @@ sub userfileupload {
                 mkdir($fullpath,0777);
             }
         }
-        open(my $fh,'>'.$fullpath.'/'.$fname);
+        open(my $fh,'>',$fullpath.'/'.$fname);
         print $fh $env{'form.'.$formname};
         close($fh);
         if ($context eq 'existingfile') {
@@ -3388,7 +3553,7 @@ sub finishuserfileupload {
 
 # Save the file
     {
-	if (!open(FH,'>'.$filepath.'/'.$file)) {
+	if (!open(FH,'>',$filepath.'/'.$file)) {
 	    &logthis('Failed to create '.$filepath.'/'.$file);
 	    print STDERR ('Failed to create '.$filepath.'/'.$file."\n");
 	    return '/adm/notfound.html';
@@ -3446,7 +3611,8 @@ sub finishuserfileupload {
         my $input = $filepath.'/'.$file;
         my $output = $filepath.'/'.'tn-'.$file;
         my $thumbsize = $thumbwidth.'x'.$thumbheight;
-        system("convert -sample $thumbsize $input $output");
+        my @args = ('convert','-sample',$thumbsize,$input,$output);
+        system({$args[0]} @args);
         if (-e $filepath.'/'.'tn-'.$file) {
             $fetchthumb  = 1; 
         }
@@ -3830,7 +3996,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)) {
@@ -3845,10 +4011,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);
 	    }
         }
@@ -3968,7 +4143,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}
@@ -4195,6 +4370,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
 #
 #
@@ -4208,7 +4572,7 @@ sub postannounce {
 
 sub getannounce {
 
-    if (open(my $fh,$perlvar{'lonDocRoot'}.'/announcement.txt')) {
+    if (open(my $fh,"<",$perlvar{'lonDocRoot'}.'/announcement.txt')) {
 	my $announcement='';
 	while (my $line = <$fh>) { $announcement .= $line; }
 	close($fh);
@@ -4271,7 +4635,7 @@ sub courseiddump {
         $coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok,
         $selfenrollonly,$catfilter,$showhidden,$caller,$cloner,$cc_clone,
         $cloneonly,$createdbefore,$createdafter,$creationcontext,$domcloner,
-        $hasuniquecode)=@_;
+        $hasuniquecode,$reqcrsdom,$reqinstcode)=@_;
     my $as_hash = 1;
     my %returnhash;
     if (!$domfilter) { $domfilter=''; }
@@ -4294,7 +4658,8 @@ sub courseiddump {
                                 &escape($catfilter), $showhidden, $caller,
                                 &escape($cloner), &escape($cc_clone), $cloneonly,
                                 &escape($createdbefore), &escape($createdafter),
-                                &escape($creationcontext), $domcloner, $hasuniquecode)));
+                                &escape($creationcontext),$domcloner,$hasuniquecode,
+                                $reqcrsdom,&escape($reqinstcode))));
                 } else {
                     $rep = &reply('courseiddump:'.&host_domain($tryserver).':'.
                              $sincefilter.':'.&escape($descfilter).':'.
@@ -4305,8 +4670,8 @@ sub courseiddump {
                              $showhidden.':'.$caller.':'.&escape($cloner).':'.
                              &escape($cc_clone).':'.$cloneonly.':'.
                              &escape($createdbefore).':'.&escape($createdafter).':'.
-                             &escape($creationcontext).':'.$domcloner.':'.$hasuniquecode,
-                             $tryserver);
+                             &escape($creationcontext).':'.$domcloner.':'.$hasuniquecode.
+                             ':'.$reqcrsdom.':'.&escape($reqinstcode),$tryserver);
                 }
 
                 my @pairs=split(/\&/,$rep);
@@ -4434,6 +4799,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 
 
 {
@@ -4448,9 +4828,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;
@@ -4459,7 +4840,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);
@@ -4471,7 +4852,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"};
 }
 
@@ -5372,9 +5753,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);
 }
@@ -5410,6 +5792,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;
                 }
@@ -5418,6 +5804,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;
@@ -5452,6 +5880,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; 
@@ -5499,6 +5928,7 @@ sub set_userprivs {
                     $thesepriv{$privilege}.=$restrictions;
                 }
                 if ($thesepriv{'adv'} eq 'F') { $adv=1; }
+                if ($thesepriv{'rar'} eq 'F') { $rar=1; }
             }
         }
         my $thesestr='';
@@ -5507,7 +5937,7 @@ sub set_userprivs {
 	}
         $userroles->{'user.priv.'.$role} = $thesestr;
     }
-    return ($author,$adv);
+    return ($author,$adv,$rar);
 }
 
 sub role_status {
@@ -5552,9 +5982,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';
@@ -5630,39 +6061,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;
@@ -5810,7 +6258,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,'.');
@@ -6044,9 +6492,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;
@@ -6161,9 +6611,9 @@ sub sixnum_code {
 # -------------------------------------------------- portfolio access checking
 
 sub portfolio_access {
-    my ($requrl) = @_;
+    my ($requrl,$clientip) = @_;
     my (undef,$udom,$unum,$file_name,$group) = &parse_portfolio_url($requrl);
-    my $result = &get_portfolio_access($udom,$unum,$file_name,$group);
+    my $result = &get_portfolio_access($udom,$unum,$file_name,$group,$clientip);
     if ($result) {
         my %setters;
         if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') {
@@ -6189,7 +6639,7 @@ sub portfolio_access {
 }
 
 sub get_portfolio_access {
-    my ($udom,$unum,$file_name,$group,$access_hash) = @_;
+    my ($udom,$unum,$file_name,$group,$clientip,$access_hash) = @_;
 
     if (!ref($access_hash)) {
 	my $current_perms = &get_portfile_permissions($udom,$unum);
@@ -6198,7 +6648,7 @@ sub get_portfolio_access {
 	$access_hash = $access_controls{$file_name};
     }
 
-    my ($public,$guest,@domains,@users,@courses,@groups);
+    my ($public,$guest,@domains,@users,@courses,@groups,@ips);
     my $now = time;
     if (ref($access_hash) eq 'HASH') {
         foreach my $key (keys(%{$access_hash})) {
@@ -6222,10 +6672,25 @@ sub get_portfolio_access {
                 push(@courses,$key);
             } elsif ($scope eq 'group') {
                 push(@groups,$key);
+            } elsif ($scope eq 'ip') {
+                push(@ips,$key);
             }
         }
         if ($public) {
             return 'ok';
+        } elsif (@ips > 0) {
+            my $allowed;
+            foreach my $ipkey (@ips) {
+                if (ref($access_hash->{$ipkey}{'ip'}) eq 'ARRAY') {
+                    if (&Apache::loncommon::check_ip_acc(join(',',@{$access_hash->{$ipkey}{'ip'}}),$clientip)) {
+                        $allowed = 1;
+                        last;
+                    }
+                }
+            }
+            if ($allowed) {
+                return 'ok';
+            }
         }
         if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') {
             if ($guest) {
@@ -6726,7 +7191,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';
@@ -6945,7 +7410,7 @@ sub allowed {
 	&& $thisallowed ne 'F' 
 	&& $thisallowed ne '2'
 	&& &is_portfolio_url($uri)) {
-	$thisallowed = &portfolio_access($uri);
+	$thisallowed = &portfolio_access($uri,$clientip);
     }
     
 # Full access at system, domain or course-wide level? Exit.
@@ -7214,7 +7679,7 @@ sub constructaccess {
     my ($ownername,$ownerdomain,$ownerhome);
 
     ($ownerdomain,$ownername) =
-        ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)/priv/($match_domain)/($match_username)/});
+        ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)/priv/($match_domain)/($match_username)(?:/|$)});
 
 # The URL does not really point to any authorspace, forget it
     unless (($ownername) && ($ownerdomain)) { return ''; }
@@ -7387,7 +7852,8 @@ sub get_commblock_resources {
                             }
                         }
                     }
-                    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);
@@ -7397,7 +7863,7 @@ 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) {
@@ -7552,7 +8018,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"; }
@@ -7580,11 +8046,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';
   }
@@ -7666,10 +8140,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);
@@ -7687,17 +8163,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;
@@ -7715,7 +8191,7 @@ sub fetch_enrollment_query {
                         if ($xml_classlist =~ /^error/) {
                             &logthis('fetch_enrollment_query - autoretrieve error: '.$xml_classlist.' for '.$filename.' from server: '.$homeserver.' '.$context.' '.$cnum);
                         } else {
-                            if ( open(FILE,">$destname") ) {
+                            if ( open(FILE,">",$destname) ) {
                                 print FILE &unescape($xml_classlist);
                                 close(FILE);
                             } else {
@@ -7732,13 +8208,19 @@ 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 2;
+    for (1..$loopmax) {
+	sleep($sleep);
         if (-e $replyfile.'.end') {
-	    if (open(my $fh,$replyfile)) {
+	    if (open(my $fh,"<",$replyfile)) {
 		$reply = join('',<$fh>);
 		close($fh);
 	   } else { return 'error: reply_file_error'; }
@@ -8158,6 +8640,107 @@ sub auto_crsreq_update {
     return \%crsreqresponse;
 }
 
+sub auto_export_grades {
+    my ($cdom,$cnum,$inforef,$gradesref) = @_;
+    my ($homeserver,%exportresponse);
+    if ($cdom =~ /^$match_domain$/) {
+        $homeserver = &domain($cdom,'primary');
+    }
+    unless (($homeserver eq 'no_host') || ($homeserver eq '')) {
+        my $info;
+        if (ref($inforef) eq 'HASH') {
+            $info = &freeze_escape($inforef);
+        }
+        if (ref($gradesref) eq 'HASH') {
+            my $grades = &freeze_escape($gradesref);
+            my $response=&reply('encrypt:autoexportgrades:'.$cdom.':'.$cnum.':'.
+                                $info.':'.$grades,$homeserver);
+            unless ($response =~ /(con_lost|error|no_such_host|refused|unknown_command)/) {
+                my @items = split(/&/,$response);
+                foreach my $item (@items) {
+                    my ($key,$value) = split('=',$item);
+                    $exportresponse{&unescape($key)} = &thaw_unescape($value);
+                }
+            }
+        }
+    }
+    return \%exportresponse;
+}
+
+sub check_instcode_cloning {
+    my ($codedefaults,$code_order,$cloner,$clonefromcode,$clonetocode) = @_;
+    unless ((ref($codedefaults) eq 'HASH') && (ref($code_order) eq 'ARRAY')) {
+        return;
+    }
+    my $canclone;
+    if (@{$code_order} > 0) {
+        my $instcoderegexp ='^';
+        my @clonecodes = split(/\&/,$cloner);
+        foreach my $item (@{$code_order}) {
+            if (grep(/^\Q$item\E=/,@clonecodes)) {
+                foreach my $pair (@clonecodes) {
+                    my ($key,$val) = split(/\=/,$pair,2);
+                    $val = &unescape($val);
+                    if ($key eq $item) {
+                        $instcoderegexp .= '('.$val.')';
+                        last;
+                    }
+                }
+            } else {
+                $instcoderegexp .= $codedefaults->{$item};
+            }
+        }
+        $instcoderegexp .= '$';
+        my (@from,@to);
+        eval {
+               (@from) = ($clonefromcode =~ /$instcoderegexp/);
+               (@to) = ($clonetocode =~ /$instcoderegexp/);
+        };
+        if ((@from > 0) && (@to > 0)) {
+            my @diffs = &Apache::loncommon::compare_arrays(\@from,\@to);
+            if (!@diffs) {
+                $canclone = 1;
+            }
+        }
+    }
+    return $canclone;
+}
+
+sub default_instcode_cloning {
+    my ($clonedom,$domdefclone,$clonefromcode,$clonetocode,$codedefaultsref,$codeorderref) = @_;
+    my (%codedefaults,@code_order,$canclone);
+    if ((ref($codedefaultsref) eq 'HASH') && (ref($codeorderref) eq 'ARRAY')) {
+        %codedefaults = %{$codedefaultsref};
+        @code_order = @{$codeorderref};
+    } elsif ($clonedom) {
+        &auto_instcode_defaults($clonedom,\%codedefaults,\@code_order);
+    }
+    if (($domdefclone) && (@code_order)) {
+        my @clonecodes = split(/\+/,$domdefclone);
+        my $instcoderegexp ='^';
+        foreach my $item (@code_order) {
+            if (grep(/^\Q$item\E$/,@clonecodes)) {
+                $instcoderegexp .= '('.$codedefaults{$item}.')';
+            } else {
+                $instcoderegexp .= $codedefaults{$item};
+            }
+        }
+        $instcoderegexp .= '$';
+        my (@from,@to);
+        eval {
+            (@from) = ($clonefromcode =~ /$instcoderegexp/);
+            (@to) = ($clonetocode =~ /$instcoderegexp/);
+        };
+        if ((@from > 0) && (@to > 0)) {
+            my @diffs = &Apache::loncommon::compare_arrays(\@from,\@to);
+            if (!@diffs) {
+                $canclone = 1;
+            }
+        }
+    }
+    return $canclone;
+}
+
 # ------------------------------------------------------- Course Group routines
 
 sub get_coursegroups {
@@ -8522,7 +9105,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')) {
@@ -8827,7 +9411,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';
@@ -8843,13 +9427,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'}) {
@@ -8896,7 +9480,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);
@@ -9261,7 +9845,7 @@ sub save_selected_files {
     my ($user, $path, @files) = @_;
     my $filename = $user."savedfiles";
     my @other_files = &files_not_in_path($user, $path);
-    open (OUT, '>'.$tmpdir.$filename);
+    open (OUT,'>',LONCAPA::tempdir().$filename);
     foreach my $file (@files) {
         print (OUT $env{'form.currentpath'}.$file."\n");
     }
@@ -9275,7 +9859,7 @@ sub save_selected_files {
 sub clear_selected_files {
     my ($user) = @_;
     my $filename = $user."savedfiles";
-    open (OUT, '>'.LONCAPA::tempdir().$filename);
+    open (OUT,'>',LONCAPA::tempdir().$filename);
     print (OUT undef);
     close (OUT);
     return ("ok");    
@@ -9285,7 +9869,7 @@ sub files_in_path {
     my ($user, $path) = @_;
     my $filename = $user."savedfiles";
     my %return_files;
-    open (IN, '<'.LONCAPA::tempdir().$filename);
+    open (IN,'<',LONCAPA::tempdir().$filename);
     while (my $line_in = <IN>) {
         chomp ($line_in);
         my @paths_and_file = split (m!/!, $line_in);
@@ -9307,7 +9891,7 @@ sub files_not_in_path {
     my $filename = $user."savedfiles";
     my @return_files;
     my $path_part;
-    open(IN, '<'.LONCAPA::.$filename);
+    open(IN,'<',LONCAPA::tempdir().$filename);
     while (my $line = <IN>) {
         #ok, I know it's clunky, but I want it to work
         my @paths_and_file = split(m|/|, $line);
@@ -9420,9 +10004,9 @@ sub modify_access_controls {
     my $tries = 0;
     my $gotlock = &newput('file_permissions',$lockhash,$domain,$user);
    
-    while (($gotlock ne 'ok') && $tries <3) {
+    while (($gotlock ne 'ok') && $tries < 10) {
         $tries ++;
-        sleep 1;
+        sleep(0.1);
         $gotlock = &newput('file_permissions',$lockhash,$domain,$user);
     }
     if ($gotlock eq 'ok') {
@@ -9715,7 +10299,23 @@ 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');
         }
@@ -9932,10 +10532,12 @@ sub get_userresdata {
     }
     #error 2 occurs when the .db doesn't exist
     if ($tmp!~/error: 2 /) {
-	&logthis("<font color=\"blue\">WARNING:".
-		 " Trying to get resource data for ".
-		 $uname." at ".$udom.": ".
-		 $tmp."</font>");
+        if ((!defined($cached)) || ($tmp ne 'con_lost')) {
+	    &logthis("<font color=\"blue\">WARNING:".
+		     " Trying to get resource data for ".
+		     $uname." at ".$udom.": ".
+		     $tmp."</font>");
+        }
     } elsif ($tmp=~/error: 2 /) {
 	#&EXT_cache_set($udom,$uname);
 	&do_cache_new('userres',$hashid,undef,600);
@@ -9949,7 +10551,7 @@ sub get_userresdata {
 #  Parameters:
 #     $name      - Course/user name.
 #     $domain    - Name of the domain the user/course is registered on.
-#     $type      - Type of thing $name is (must be 'course' or 'user'
+#     $type      - Type of thing $name is (must be 'course' or 'user')
 #     @which     - Array of names of resources desired.
 #  Returns:
 #     The value of the first reasource in @which that is found in the
@@ -9968,13 +10570,44 @@ sub resdata {
     }
     if (!ref($result)) { return $result; }    
     foreach my $item (@which) {
-	if (defined($result->{$item->[0]})) {
-	    return [$result->{$item->[0]},$item->[1]];
-	}
+        if (ref($item) eq 'ARRAY') {
+	    if (defined($result->{$item->[0]})) {
+	        return [$result->{$item->[0]},$item->[1]];
+	    }
+        }
     }
     return undef;
 }
 
+sub get_domain_ltitools {
+    my ($cdom) = @_;
+    my %ltitools;
+    my ($result,$cached)=&is_cached_new('ltitools',$cdom);
+    if (defined($cached)) {
+        if (ref($result) eq 'HASH') {
+            %ltitools = %{$result};
+        }
+    } else {
+        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);
+    }
+    return %ltitools;
+}
+
 sub get_numsuppfiles {
     my ($cnum,$cdom,$ignorecache)=@_;
     my $hashid=$cnum.':'.$cdom;
@@ -10423,13 +11056,14 @@ sub add_prefix_and_part {
 
 my %metaentry;
 my %importedpartids;
+my %importedrespids;
 sub metadata {
     my ($uri,$what,$liburi,$prefix,$depthcount)=@_;
     $uri=&declutter($uri);
     # if it is a non metadata possible uri return quickly
     if (($uri eq '') || 
 	(($uri =~ m|^/*adm/|) && 
-	     ($uri !~ m|^adm/includes|) && ($uri !~ m{/(smppg|bulletinboard)$})) ||
+	     ($uri !~ m|^adm/includes|) && ($uri !~ m{/(smppg|bulletinboard|ext\.tool)$})) ||
         ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ m{^/*uploaded/.+\.sequence$})) {
 	return undef;
     }
@@ -10450,9 +11084,11 @@ sub metadata {
     }
     {
 # Imported parts would go here
-        my %importedids=();
-        my @origfileimportpartids=();
+        my @origfiletagids=();
         my $importedparts=0;
+
+# Imported responseids would go here
+        my $importedresponses=0;
 #
 # Is this a recursive call for a library?
 #
@@ -10547,8 +11183,37 @@ sub metadata {
                         my $dir=$filename;
                         $dir=~s|[^/]*$||;
                         $location=&filelocation($dir,$location);
-                       
+
+                        my $importid=$token->[2]->{'id'};
                         my $importmode=$token->[2]->{'importmode'};
+#
+# Check metadata for imported file to
+# see if it contained response items
+#
+                        my %currmetaentry = %metaentry;
+                        my $libresponseorder = &metadata($location,'responseorder');
+                        my $origfile;
+                        if ($libresponseorder ne '') {
+                            if ($#origfiletagids<0) {
+                                undef(%importedrespids);
+                                undef(%importedpartids);
+                            }
+                            @{$importedrespids{$importid}} = split(/\s*,\s*/,$libresponseorder);
+                            if (@{$importedrespids{$importid}} > 0) {
+                                $importedresponses = 1;
+# We need to get the original file and the imported file to get the response order correct
+# Load and inspect original file
+                                if ($#origfiletagids<0) {
+                                    my $origfilelocation=$perlvar{'lonDocRoot'}.&clutter($uri);
+                                    $origfile=&getfile($origfilelocation);
+                                    @origfiletagids=($origfile=~/<((?:\w+)response|import|part)[^>]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs);
+                                }
+                            }
+                        }
+# Do not overwrite contents of %metaentry hash for resource itself with 
+# hash populated for imported library file
+                        %metaentry = %currmetaentry;
+                        undef(%currmetaentry);
                         if ($importmode eq 'problem') {
 # Import as problem/response
                            $unikey=&add_prefix_and_part($prefix,$token->[2]->{'part'});
@@ -10557,12 +11222,15 @@ sub metadata {
                            $importedparts=1;
 # We need to get the original file and the imported file to get the part order correct
 # Good news: we do not need to worry about nested libraries, since parts cannot be nested
-# Load and inspect original file
-                           if ($#origfileimportpartids<0) {
-                              undef(%importedpartids);
-                              my $origfilelocation=$perlvar{'lonDocRoot'}.&clutter($uri);
-                              my $origfile=&getfile($origfilelocation);
-                              @origfileimportpartids=($origfile=~/<(part|import)[^>]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs);
+# Load and inspect original file if we didn't do that already
+                           if ($#origfiletagids<0) {
+                               undef(%importedrespids);
+                               undef(%importedpartids);
+                               if ($origfile eq '') {
+                                   my $origfilelocation=$perlvar{'lonDocRoot'}.&clutter($uri);
+                                   $origfile=&getfile($origfilelocation);
+                                   @origfiletagids=($origfile=~/<(part|import)[^>]*id\s*=\s*[\"\']([^\"\']+)[\"\'][^>]*>/gs);
+                               }
                            }
 
 # Load and inspect imported file
@@ -10676,20 +11344,48 @@ sub metadata {
 	    grep { ! $seen{$_} ++ } (split(',',$metaentry{':packages'}));
 	$metaentry{':packages'} = join(',',@uniq_packages);
 
-        if ($importedparts) {
+        if (($importedresponses) || ($importedparts)) {
+            if ($importedparts) {
 # We had imported parts and need to rebuild partorder
-           $metaentry{':partorder'}='';
-           $metathesekeys{'partorder'}=1;
-           for (my $index=0;$index<$#origfileimportpartids;$index+=2) {
-               if ($origfileimportpartids[$index] eq 'part') {
-# original part, part of the problem
-                  $metaentry{':partorder'}.=','.$origfileimportpartids[$index+1];
-               } else {
-# we have imported parts at this position
-                  $metaentry{':partorder'}.=','.$importedpartids{$origfileimportpartids[$index+1]};
-               }
-           }
-           $metaentry{':partorder'}=~s/^\,//;
+                $metaentry{':partorder'}='';
+                $metathesekeys{'partorder'}=1;
+            }
+            if ($importedresponses) {
+# We had imported responses and need to rebuild responseorder
+                $metaentry{':responseorder'}='';
+                $metathesekeys{'responseorder'}=1;
+            }
+            for (my $index=0;$index<$#origfiletagids;$index+=2) {
+                my $origid = $origfiletagids[$index+1];
+                if ($origfiletagids[$index] eq 'part') {
+# Original part, part of the problem
+                    if ($importedparts) {
+                        $metaentry{':partorder'}.=','.$origid;
+                    }
+                } elsif ($origfiletagids[$index] eq 'import') {
+                    if ($importedparts) {
+# We have imported parts at this position
+                        $metaentry{':partorder'}.=','.$importedpartids{$origid};
+                    }
+                    if ($importedresponses) {
+# We have imported responses at this position
+                        if (ref($importedrespids{$origid}) eq 'ARRAY') {
+                            $metaentry{':responseorder'}.=','.join(',',map { $origid.'_'.$_ } @{$importedrespids{$origid}});
+                        }
+                    }
+                } else {
+# Original response item, part of the problem
+                    if ($importedresponses) {
+                        $metaentry{':responseorder'}.=','.$origid;
+                    }
+                }
+            }
+            if ($importedparts) {
+                $metaentry{':partorder'}=~s/^\,//;
+            }
+            if ($importedresponses) {
+                $metaentry{':responseorder'}=~s/^\,//;
+            }
         }
 
 	$metaentry{':keys'} = join(',',keys(%metathesekeys));
@@ -11777,7 +12473,7 @@ sub readfile {
     my $file = shift;
     if ( (! -e $file ) || ($file eq '') ) { return -1; };
     my $fh;
-    open($fh,"<$file");
+    open($fh,"<",$file);
     my $a='';
     while (my $line = <$fh>) { $a .= $line; }
     return $a;
@@ -11890,7 +12586,7 @@ sub machine_ids {
 
 sub additional_machine_domains {
     my @domains;
-    open(my $fh,"<$perlvar{'lonTabDir'}/expected_domains.tab");
+    open(my $fh,"<","$perlvar{'lonTabDir'}/expected_domains.tab");
     while( my $line = <$fh>) {
         $line =~ s/\s//g;
         push(@domains,$line);
@@ -11961,6 +12657,8 @@ sub clutter {
 #		&logthis("Got a blank emb style");
 	    }
 	}
+    } elsif ($thisfn =~ m{^/adm/$match_domain/$match_courseid/\d+/ext\.tool$}) {
+        $thisfn='/adm/wrapper'.$thisfn;
     }
     return $thisfn;
 }
@@ -12034,7 +12732,7 @@ sub get_dns {
     }
 
     my %alldns;
-    open(my $config,"<$perlvar{'lonTabDir'}/hosts.tab");
+    open(my $config,"<","$perlvar{'lonTabDir'}/hosts.tab");
     foreach my $dns (<$config>) {
 	next if ($dns !~ /^\^(\S*)/x);
         my $line = $1;
@@ -12062,7 +12760,7 @@ sub get_dns {
     close($config);
     my $which = (split('/',$url))[3];
     &logthis("unable to contact DNS defaulting to on disk file dns_$which.tab\n");
-    open($config,"<$perlvar{'lonTabDir'}/dns_$which.tab");
+    open($config,"<","$perlvar{'lonTabDir'}/dns_$which.tab");
     my @content = <$config>;
     &$func(\@content,$hashref);
     return;
@@ -12152,10 +12850,10 @@ sub fetch_dns_checksums {
     }
 
     sub load_domain_tab {
-	my ($ignore_cache) = @_;
-	&get_dns('/adm/dns/domain',\&parse_domain_tab,$ignore_cache);
+	my ($ignore_cache,$nocache) = @_;
+	&get_dns('/adm/dns/domain',\&parse_domain_tab,$ignore_cache,$nocache);
 	my $fh;
-	if (open($fh,"<".$perlvar{'lonTabDir'}.'/domain.tab')) {
+	if (open($fh,"<",$perlvar{'lonTabDir'}.'/domain.tab')) {
 	    my @lines = <$fh>;
 	    &parse_domain_tab(\@lines);
 	}
@@ -12207,8 +12905,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)) {
@@ -12239,9 +12952,9 @@ sub fetch_dns_checksums {
     }
 
     sub load_hosts_tab {
-	my ($ignore_cache) = @_;
-	&get_dns('/adm/dns/hosts',\&parse_hosts_tab,$ignore_cache);
-	open(my $config,"<$perlvar{'lonTabDir'}/hosts.tab");
+	my ($ignore_cache,$nocache) = @_;
+	&get_dns('/adm/dns/hosts',\&parse_hosts_tab,$ignore_cache,$nocache);
+	open(my $config,"<","$perlvar{'lonTabDir'}/hosts.tab");
 	my @config = <$config>;
 	&parse_hosts_tab(\@config);
 	close($config);
@@ -12262,7 +12975,8 @@ sub fetch_dns_checksums {
     }
 
     sub all_names {
-	&load_hosts_tab() if (!$loaded);
+        my ($ignore_cache,$nocache) = @_;
+	&load_hosts_tab($ignore_cache,$nocache) if (!$loaded);
 
 	return %name_to_host;
     }
@@ -12384,7 +13098,7 @@ sub fetch_dns_checksums {
     }
     
     sub get_iphost {
-	my ($ignore_cache) = @_;
+	my ($ignore_cache,$nocache) = @_;
 
 	if (!$ignore_cache) {
 	    if (%iphost) {
@@ -12408,7 +13122,7 @@ sub fetch_dns_checksums {
 	    %old_name_to_ip = %{$ip_info->[1]};
 	}
 
-	my %name_to_host = &all_names();
+	my %name_to_host = &all_names($ignore_cache,$nocache);
 	foreach my $name (keys(%name_to_host)) {
 	    my $ip;
 	    if (!exists($name_to_ip{$name})) {
@@ -12433,9 +13147,11 @@ sub fetch_dns_checksums {
 	    }
 	    push(@{$iphost{$ip}},@{$name_to_host{$name}});
 	}
-	&do_cache_new('iphost','iphost',
-		      [\%iphost,\%name_to_ip,\%lonid_to_ip],
-		      48*60*60);
+        unless ($nocache) {
+	    &do_cache_new('iphost','iphost',
+		          [\%iphost,\%name_to_ip,\%lonid_to_ip],
+		          48*60*60);
+        }
 
 	return %iphost;
     }
@@ -12504,7 +13220,7 @@ sub all_loncaparevs {
 {
     sub load_loncaparevs {
         if (-e "$perlvar{'lonTabDir'}/loncaparevs.tab") {
-            if (open(my $config,"<$perlvar{'lonTabDir'}/loncaparevs.tab")) {
+            if (open(my $config,"<","$perlvar{'lonTabDir'}/loncaparevs.tab")) {
                 while (my $configline=<$config>) {
                     chomp($configline);
                     my ($hostid,$loncaparev)=split(/:/,$configline);
@@ -12520,7 +13236,7 @@ sub all_loncaparevs {
 {
     sub load_serverhomeIDs {
         if (-e "$perlvar{'lonTabDir'}/serverhomeIDs.tab") {
-            if (open(my $config,"<$perlvar{'lonTabDir'}/serverhomeIDs.tab")) {
+            if (open(my $config,"<","$perlvar{'lonTabDir'}/serverhomeIDs.tab")) {
                 while (my $configline=<$config>) {
                     chomp($configline);
                     my ($name,$id)=split(/:/,$configline);
@@ -12545,7 +13261,7 @@ BEGIN {
 
 # ------------------------------------------------------ Read spare server file
 {
-    open(my $config,"<$perlvar{'lonTabDir'}/spare.tab");
+    open(my $config,"<","$perlvar{'lonTabDir'}/spare.tab");
 
     while (my $configline=<$config>) {
        chomp($configline);
@@ -12559,7 +13275,7 @@ BEGIN {
 }
 # ------------------------------------------------------------ Read permissions
 {
-    open(my $config,"<$perlvar{'lonTabDir'}/roles.tab");
+    open(my $config,"<","$perlvar{'lonTabDir'}/roles.tab");
 
     while (my $configline=<$config>) {
 	chomp($configline);
@@ -12573,7 +13289,7 @@ BEGIN {
 
 # -------------------------------------------- Read plain texts for permissions
 {
-    open(my $config,"<$perlvar{'lonTabDir'}/rolesplain.tab");
+    open(my $config,"<","$perlvar{'lonTabDir'}/rolesplain.tab");
 
     while (my $configline=<$config>) {
 	chomp($configline);
@@ -12593,7 +13309,7 @@ BEGIN {
 
 # ---------------------------------------------------------- Read package table
 {
-    open(my $config,"<$perlvar{'lonTabDir'}/packages.tab");
+    open(my $config,"<","$perlvar{'lonTabDir'}/packages.tab");
 
     while (my $configline=<$config>) {
 	if ($configline !~ /\S/ || $configline=~/^#/) { next; }
@@ -12639,7 +13355,7 @@ BEGIN {
 # ---------------------------------------------------------- Read managers table
 {
     if (-e "$perlvar{'lonTabDir'}/managers.tab") {
-        if (open(my $config,"<$perlvar{'lonTabDir'}/managers.tab")) {
+        if (open(my $config,"<","$perlvar{'lonTabDir'}/managers.tab")) {
             while (my $configline=<$config>) {
                 chomp($configline);
                 next if ($configline =~ /^\#/);
@@ -12993,9 +13709,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 *
 
@@ -13199,6 +13916,8 @@ Inputs:
 
 =item $credits, number of credits student will earn from this class
 
+=item $instsec, institutional course section code for student
+
 =back
 
 
@@ -13688,7 +14407,8 @@ for course's uploaded content.
 =over
 
 =item
-canuse_pdfforms, officialcredits, unofficialcredits, textbookcredits, officialquota, unofficialquota, communityquota, textbookquota
+canuse_pdfforms, officialcredits, unofficialcredits, textbookcredits, officialquota, unofficialquota,
+communityquota, textbookquota
 
 =back