--- loncom/lonnet/perl/lonnet.pm	2006/11/10 02:04:31	1.802
+++ loncom/lonnet/perl/lonnet.pm	2007/03/28 21:44:13	1.854
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.802 2006/11/10 02:04:31 raeburn Exp $
+# $Id: lonnet.pm,v 1.854 2007/03/28 21:44:13 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -35,12 +35,10 @@ use HTTP::Headers;
 use HTTP::Date;
 # use Date::Parse;
 use vars 
-qw(%perlvar %hostname %badServerCache %iphost %spareid %hostdom 
-   %libserv %pr %prp $memcache %packagetab 
+qw(%perlvar %badServerCache %spareid 
+   %pr %prp $memcache %packagetab 
    %courselogs %accesshash %userrolehash %domainrolehash $processmarker $dumpcount 
    %coursedombuf %coursenumbuf %coursehombuf %coursedescrbuf %courseinstcodebuf %courseownerbuf %coursetypebuf
-   %domaindescription %domain_auth_def %domain_auth_arg_def 
-   %domain_lang_def %domain_city %domain_longi %domain_lati %domain_primary
    $tmpdir $_64bit %env);
 
 use IO::Socket;
@@ -53,9 +51,9 @@ use Time::HiRes qw( gettimeofday tv_inte
 use Cache::Memcached;
 use Digest::MD5;
 use Math::Random;
-use lib '/home/httpd/lib/perl';
-use LONCAPA;
+use LONCAPA qw(:DEFAULT :match);
 use LONCAPA::Configuration;
+use Apache::lonhosts;
 
 my $readit;
 my $max_connection_retries = 10;     # Or some such value.
@@ -147,10 +145,24 @@ sub logperm {
     return 1;
 }
 
+sub create_connection {
+    my ($hostname,$lonid) = @_;
+    my $client=IO::Socket::UNIX->new(Peer    => $perlvar{'lonSockCreate'},
+				     Type    => SOCK_STREAM,
+				     Timeout => 10);
+    return 0 if (!$client);
+    print $client (join(':',$hostname,$lonid,&machine_ids($lonid))."\n");
+    my $result = <$client>;
+    chomp($result);
+    return 1 if ($result eq 'done');
+    return 0;
+}
+
+
 # -------------------------------------------------- Non-critical communication
 sub subreply {
     my ($cmd,$server)=@_;
-    my $peerfile="$perlvar{'lonSockDir'}/".$hostname{$server};
+    my $peerfile="$perlvar{'lonSockDir'}/".&hostname($server);
     #
     #  With loncnew process trimming, there's a timing hole between lonc server
     #  process exit and the master server picking up the listen on the AF_UNIX
@@ -173,8 +185,10 @@ sub subreply {
 				      Timeout => 10);
 	if($client) {
 	    last;		# Connected!
+	} else {
+	    &create_connection(&hostname($server),$server);
 	}
-	sleep(1);		# Try again later if failed connection.
+        sleep(1);		# Try again later if failed connection.
     }
     my $answer;
     if ($client) {
@@ -190,7 +204,7 @@ sub subreply {
 
 sub reply {
     my ($cmd,$server)=@_;
-    unless (defined($hostname{$server})) { return 'no_such_host'; }
+    unless (defined(&hostname($server))) { return 'no_such_host'; }
     my $answer=subreply($cmd,$server);
     if (($answer=~/^refused/) || ($answer=~/^rejected/)) {
        &logthis("<font color=\"blue\">WARNING:".
@@ -202,8 +216,7 @@ sub reply {
 # ----------------------------------------------------------- Send USR1 to lonc
 
 sub reconlonc {
-    my $peerfile=shift;
-    &logthis("Trying to reconnect for $peerfile");
+    &logthis("Trying to reconnect lonc");
     my $loncfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
     if (open(my $fh,"<$loncfile")) {
 	my $loncpid=<$fh>;
@@ -212,19 +225,13 @@ sub reconlonc {
 	    &logthis("lonc at pid $loncpid responding, sending USR1");
             kill USR1 => $loncpid;
             sleep 1;
-            if (-e "$peerfile") { return; }
-            &logthis("$peerfile still not there, give it another try");
-            sleep 5;
-            if (-e "$peerfile") { return; }
-            &logthis(
-  "<font color=\"blue\">WARNING: $peerfile still not there, giving up</font>");
-        } else {
+         } else {
 	    &logthis(
                "<font color=\"blue\">WARNING:".
                " lonc at pid $loncpid not responding, giving up</font>");
         }
     } else {
-     &logthis('<font color="blue">WARNING: lonc not running, giving up</font>');
+	&logthis('<font color="blue">WARNING: lonc not running, giving up</font>');
     }
 }
 
@@ -232,7 +239,7 @@ sub reconlonc {
 
 sub critical {
     my ($cmd,$server)=@_;
-    unless ($hostname{$server}) {
+    unless (&hostname($server)) {
         &logthis("<font color=\"blue\">WARNING:".
                " Critical message to unknown server ($server)</font>");
         return 'no_such_host';
@@ -368,6 +375,26 @@ sub transfer_profile_to_env {
     }
 }
 
+sub timed_flock {
+    my ($file,$lock_type) = @_;
+    my $failed=0;
+    eval {
+	local $SIG{__DIE__}='DEFAULT';
+	local $SIG{ALRM}=sub {
+	    $failed=1;
+	    die("failed lock");
+	};
+	alarm(13);
+	flock($file,$lock_type);
+	alarm(0);
+    };
+    if ($failed) {
+	return undef;
+    } else {
+	return 1;
+    }
+}
+
 # ---------------------------------------------------------- Append Environment
 
 sub appenv {
@@ -382,8 +409,11 @@ sub appenv {
             $env{$key}=$newenv{$key};
         }
     }
-    if (tie(my %disk_env,'GDBM_File',$env{'user.environment'},&GDBM_WRITER(),
-	    0640)) {
+    open(my $env_file,$env{'user.environment'});
+    if (&timed_flock($env_file,LOCK_EX)
+	&&
+	tie(my %disk_env,'GDBM_File',$env{'user.environment'},
+	    (&GDBM_WRITER()|&GDBM_NOLOCK()),0640)) {
 	while (my ($key,$value) = each(%newenv)) {
 	    $disk_env{$key} = $value;
 	}
@@ -400,8 +430,11 @@ sub delenv {
                 "Attempt to delete from environment ".$delthis);
         return 'error';
     }
-    if (tie(my %disk_env,'GDBM_File',$env{'user.environment'},&GDBM_WRITER(),
-	    0640)) {
+    open(my $env_file,$env{'user.environment'});
+    if (&timed_flock($env_file,LOCK_EX)
+	&&
+	tie(my %disk_env,'GDBM_File',$env{'user.environment'},
+	    (&GDBM_WRITER()|&GDBM_NOLOCK()),0640)) {
 	foreach my $key (keys(%disk_env)) {
 	    if ($key=~/^$delthis/) { 
                 delete($env{$key});
@@ -499,7 +532,7 @@ sub spareserver {
     }
 
     if (!$want_server_name) {
-	$spare_server="http://$hostname{$spare_server}";
+	$spare_server="http://".&hostname($spare_server);
     }
     return $spare_server;
 }
@@ -588,11 +621,17 @@ sub queryauthenticate {
 
 sub authenticate {
     my ($uname,$upass,$udom)=@_;
-    $upass=escape($upass);
-    $uname=~s/\W//g;
-    my $uhome=&homeserver($uname,$udom);
-    if (!$uhome) {
-	&logthis("User $uname at $udom is unknown in authenticate");
+    $upass=&escape($upass);
+    $uname= &LONCAPA::clean_username($uname);
+    my $uhome=&homeserver($uname,$udom,1);
+    if ((!$uhome) || ($uhome eq 'no_host')) {
+# Maybe the machine was offline and only re-appeared again recently?
+        &reconlonc();
+# One more
+	my $uhome=&homeserver($uname,$udom,1);
+	if ((!$uhome) || ($uhome eq 'no_host')) {
+	    &logthis("User $uname at $udom is unknown in authenticate");
+	}
 	return 'no_host';
     }
     my $answer=reply("encrypt:auth:$udom:$uname:$upass",$uhome);
@@ -616,18 +655,19 @@ sub homeserver {
     my $index="$uname:$udom";
 
     if (exists($homecache{$index})) { return $homecache{$index}; }
-    my $tryserver;
-    foreach $tryserver (keys %libserv) {
+
+    my %servers = &get_servers($udom,'library');
+    foreach my $tryserver (keys(%servers)) {
         next if ($ignoreBadCache ne 'true' && 
 		 exists($badServerCache{$tryserver}));
-	if ($hostdom{$tryserver} eq $udom) {
-           my $answer=reply("home:$udom:$uname",$tryserver);
-           if ($answer eq 'found') { 
-	       return $homecache{$index}=$tryserver;
-           } elsif ($answer eq 'no_host') {
-	       $badServerCache{$tryserver}=1;
-           }
-       }
+
+	my $answer=reply("home:$udom:$uname",$tryserver);
+	if ($answer eq 'found') {
+	    delete($badServerCache{$tryserver}); 
+	    return $homecache{$index}=$tryserver;
+	} elsif ($answer eq 'no_host') {
+	    $badServerCache{$tryserver}=1;
+	}
     }    
     return 'no_host';
 }
@@ -638,24 +678,22 @@ sub idget {
     my ($udom,@ids)=@_;
     my %returnhash=();
     
-    my $tryserver;
-    foreach $tryserver (keys %libserv) {
-       if ($hostdom{$tryserver} eq $udom) {
-	  my $idlist=join('&',@ids);
-          $idlist=~tr/A-Z/a-z/; 
-	  my $reply=&reply("idget:$udom:".$idlist,$tryserver);
-          my @answer=();
-          if (($reply ne 'con_lost') && ($reply!~/^error\:/)) {
-	      @answer=split(/\&/,$reply);
-          }                    ;
-          my $i;
-          for ($i=0;$i<=$#ids;$i++) {
-              if ($answer[$i]) {
-		  $returnhash{$ids[$i]}=$answer[$i];
-              } 
-          }
-       }
-    }    
+    my %servers = &get_servers($udom,'library');
+    foreach my $tryserver (keys(%servers)) {
+	my $idlist=join('&',@ids);
+	$idlist=~tr/A-Z/a-z/; 
+	my $reply=&reply("idget:$udom:".$idlist,$tryserver);
+	my @answer=();
+	if (($reply ne 'con_lost') && ($reply!~/^error\:/)) {
+	    @answer=split(/\&/,$reply);
+	}                    ;
+	my $i;
+	for ($i=0;$i<=$#ids;$i++) {
+	    if ($answer[$i]) {
+		$returnhash{$ids[$i]}=$answer[$i];
+	    } 
+	}
+    } 
     return %returnhash;
 }
 
@@ -694,6 +732,77 @@ sub idput {
     }
 }
 
+# ------------------------------------------- get items from domain db files   
+
+sub get_dom {
+    my ($namespace,$storearr,$udom)=@_;
+    my $items='';
+    foreach my $item (@$storearr) {
+        $items.=&escape($item).'&';
+    }
+    $items=~s/\&$//;
+    if (!$udom) { $udom=$env{'user.domain'}; }
+    if (defined(&domain($udom,'primary'))) {
+        my $uhome=&domain($udom,'primary');
+        my $rep=&reply("getdom:$udom:$namespace:$items",$uhome);
+        my @pairs=split(/\&/,$rep);
+        if ( $#pairs==0 && $pairs[0] =~ /^(con_lost|error|no_such_host)/i) {
+            return @pairs;
+        }
+        my %returnhash=();
+        my $i=0;
+        foreach my $item (@$storearr) {
+            $returnhash{$item}=&thaw_unescape($pairs[$i]);
+            $i++;
+        }
+        return %returnhash;
+    } else {
+        &logthis("get_dom failed - no primary domain server for $udom");
+    }
+}
+
+# -------------------------------------------- put items in domain db files 
+
+sub put_dom {
+    my ($namespace,$storehash,$udom)=@_;
+    if (!$udom) { $udom=$env{'user.domain'}; }
+    if (defined(&domain($udom,'primary'))) {
+        my $uhome=&domain($udom,'primary');
+        my $items='';
+        foreach my $item (keys(%$storehash)) {
+            $items.=&escape($item).'='.&freeze_escape($$storehash{$item}).'&';
+        }
+        $items=~s/\&$//;
+        return &reply("putdom:$udom:$namespace:$items",$uhome);
+    } else {
+        &logthis("put_dom failed - no primary domain server for $udom");
+    }
+}
+
+sub retrieve_inst_usertypes {
+    my ($udom) = @_;
+    my (%returnhash,@order);
+    if (defined(&domain($udom,'primary'))) {
+        my $uhome=&domain($udom,'primary');
+        my $rep=&reply("inst_usertypes:$udom",$uhome);
+        my ($hashitems,$orderitems) = split(/:/,$rep); 
+        my @pairs=split(/\&/,$hashitems);
+        foreach my $item (@pairs) {
+            my ($key,$value)=split(/=/,$item,2);
+            $key = &unescape($key);
+            next if ($key =~ /^error: 2 /);
+            $returnhash{$key}=&thaw_unescape($value);
+        }
+        my @esc_order = split(/\&/,$orderitems);
+        foreach my $item (@esc_order) {
+            push(@order,&unescape($item));
+        }
+    } else {
+        &logthis("get_dom failed - no primary domain server for $udom");
+    }
+    return (\%returnhash,\@order);
+}
+
 # --------------------------------------------------- Assign a key to a student
 
 sub assign_access_key {
@@ -827,17 +936,32 @@ sub validate_access_key {
 # ------------------------------------- Find the section of student in a course
 sub devalidate_getsection_cache {
     my ($udom,$unam,$courseid)=@_;
-    $courseid=~s/\_/\//g;
-    $courseid=~s/^(\w)/\/$1/;
     my $hashid="$udom:$unam:$courseid";
     &devalidate_cache_new('getsection',$hashid);
 }
 
+sub courseid_to_courseurl {
+    my ($courseid) = @_;
+    #already url style courseid
+    return $courseid if ($courseid =~ m{^/});
+
+    if (exists($env{'course.'.$courseid.'.num'})) {
+	my $cnum = $env{'course.'.$courseid.'.num'};
+	my $cdom = $env{'course.'.$courseid.'.domain'};
+	return "/$cdom/$cnum";
+    }
+
+    my %courseinfo=&Apache::lonnet::coursedescription($courseid);
+    if (exists($courseinfo{'num'})) {
+	return "/$courseinfo{'domain'}/$courseinfo{'num'}";
+    }
+
+    return undef;
+}
+
 sub getsection {
     my ($udom,$unam,$courseid)=@_;
     my $cachetime=1800;
-    $courseid=~s/\_/\//g;
-    $courseid=~s/^(\w)/\/$1/;
 
     my $hashid="$udom:$unam:$courseid";
     my ($result,$cached)=&is_cached_new('getsection',$hashid);
@@ -858,14 +982,13 @@ sub getsection {
     # If there is more than one expired role, choose the one which ended last.
     # If there is a role which has expired, return it.
     #
-    foreach my $line (split(/\&/,&reply('dump:'.$udom.':'.$unam.':roles',
-					&homeserver($unam,$udom)))) {
-        my ($key,$value)=split(/\=/,$line,2);
-        $key=&unescape($key);
+    $courseid = &courseid_to_courseurl($courseid);
+    my %roleshash = &dump('roles',$udom,$unam,$courseid);
+    foreach my $key (keys(%roleshash)) {
         next if ($key !~/^\Q$courseid\E(?:\/)*(\w+)*\_st$/);
         my $section=$1;
         if ($key eq $courseid.'_st') { $section=''; }
-        my ($dummy,$end,$start)=split(/\_/,&unescape($value));
+        my ($dummy,$end,$start)=split(/\_/,&unescape($roleshash{$key}));
         my $now=time;
         if (defined($end) && $end && ($now > $end)) {
             $Expired{$end}=$section;
@@ -904,10 +1027,16 @@ my %remembered;
 my %accessed;
 my $kicks=0;
 my $hits=0;
+sub make_key {
+    my ($name,$id) = @_;
+    if (length($id) > 200) { $id=length($id).':'.&Digest::MD5::md5_hex($id); }
+    return &escape($name.':'.$id);
+}
+
 sub devalidate_cache_new {
     my ($name,$id,$debug) = @_;
     if ($debug) { &Apache::lonnet::logthis("deleting $name:$id"); }
-    $id=&escape($name.':'.$id);
+    $id=&make_key($name,$id);
     $memcache->delete($id);
     delete($remembered{$id});
     delete($accessed{$id});
@@ -915,7 +1044,7 @@ sub devalidate_cache_new {
 
 sub is_cached_new {
     my ($name,$id,$debug) = @_;
-    $id=&escape($name.':'.$id);
+    $id=&make_key($name,$id);
     if (exists($remembered{$id})) {
 	if ($debug) { &Apache::lonnet::logthis("Earyl return $id of $remembered{$id} "); }
 	$accessed{$id}=[&gettimeofday()];
@@ -938,7 +1067,7 @@ sub is_cached_new {
 
 sub do_cache_new {
     my ($name,$id,$value,$time,$debug) = @_;
-    $id=&escape($name.':'.$id);
+    $id=&make_key($name,$id);
     my $setvalue=$value;
     if (!defined($setvalue)) {
 	$setvalue='__undef__';
@@ -1116,6 +1245,7 @@ sub repcopy {
     }
     $filename=~s/[\n\r]//g;
     my $transname="$filename.in.transfer";
+# FIXME: this should flock
     if ((-e $filename) || (-e $transname)) { return 'ok'; }
     my $remoteurl=subscribe($filename);
     if ($remoteurl =~ /^con_lost by/) {
@@ -1364,15 +1494,17 @@ sub store_edited_file {
 }
 
 sub clean_filename {
-    my ($fname)=@_;
+    my ($fname,$args)=@_;
 # Replace Windows backslashes by forward slashes
     $fname=~s/\\/\//g;
-# Get rid of everything but the actual filename
-    $fname=~s/^.*\/([^\/]+)$/$1/;
+    if (!$args->{'keep_path'}) {
+        # Get rid of everything but the actual filename
+	$fname=~s/^.*\/([^\/]+)$/$1/;
+    }
 # Replace spaces by underscores
     $fname=~s/\s+/\_/g;
 # Replace all other weird characters by nothing
-    $fname=~s/[^\w\.\-]//g;
+    $fname=~s{[^/\w\.\-]}{}g;
 # Replace all .\d. sequences with _\d. so they no longer look like version
 # numbers
     $fname=~s/\.(\d+)(?=\.)/_$1/g;
@@ -1641,6 +1773,12 @@ sub removeuserfile {
         if (($fname !~ /\.meta$/) && (&is_portfolio_file($fname))) {
             my $metafile = $fname.'.meta';
             my $metaresult = &removeuserfile($docuname,$docudom,$metafile); 
+	    my $url = "/uploaded/$docudom/$docuname/$fname";
+            my ($file,$group) = (&parse_portfolio_url($url))[3,4];
+            my $sqlresult = 
+                &update_portfolio_table($docuname,$docudom,$file,
+                                        'portfolio_metadata',$group,
+                                        'delete');
         }
     }
     return $result;
@@ -1663,6 +1801,12 @@ sub renameuserfile {
             my $newmeta = $new.'.meta';
             my $metaresult = 
                 &renameuserfile($docuname,$docudom,$oldmeta,$newmeta);
+	    my $url = "/uploaded/$docudom/$docuname/$old";
+            my ($file,$group) = (&parse_portfolio_url($url))[3,4];
+            my $sqlresult = 
+                &update_portfolio_table($docuname,$docudom,$file,
+                                        'portfolio_metadata',$group,
+                                        'delete');
         }
     }
     return $result;
@@ -1718,8 +1862,9 @@ sub flushcourselogs {
 # Write course id database (reverse lookup) to homeserver of courses 
 # Is used in pickcourse
 #
-    foreach my $crsid (keys(%courseidbuffer)) {
-        &courseidput($hostdom{$crsid},$courseidbuffer{$crsid},$crsid);
+    foreach my $crs_home (keys(%courseidbuffer)) {
+        &courseidput(&host_domain($crs_home),$courseidbuffer{$crs_home},
+		     $crs_home);
     }
 #
 # File accesses
@@ -1728,7 +1873,8 @@ sub flushcourselogs {
     foreach my $entry (keys(%accesshash)) {
         if ($entry =~ /___count$/) {
             my ($dom,$name);
-            ($dom,$name,undef)=($entry=~m:___(\w+)/(\w+)/(.*)___count$:);
+            ($dom,$name,undef)=
+		($entry=~m{___($match_domain)/($match_name)/(.*)___count$});
             if (! defined($dom) || $dom eq '' || 
                 ! defined($name) || $name eq '') {
                 my $cid = $env{'request.course.id'};
@@ -1749,7 +1895,7 @@ sub flushcourselogs {
                 }
             }
         } else {
-            my ($dom,$name) = ($entry=~m:___(\w+)/(\w+)/(.*)___(\w+)$:);
+            my ($dom,$name) = ($entry=~m{___($match_domain)/($match_name)/(.*)___(\w+)$});
             my %temphash=($entry => $accesshash{$entry});
             if (&put('nohist_resevaldata',\%temphash,$dom,$name) eq 'ok') {
                 delete $accesshash{$entry};
@@ -1785,13 +1931,12 @@ sub flushcourselogs {
         delete $domainrolehash{$entry};
     }
     foreach my $dom (keys(%domrolebuffer)) {
-        foreach my $tryserver (keys %libserv) {
-            if ($hostdom{$tryserver} eq $dom) {
-                unless (&reply('domroleput:'.$dom.':'.
-                  $domrolebuffer{$dom},$tryserver) eq 'ok') {
-                    &logthis('Put of domain roles failed for '.$dom.' and  '.$tryserver);
-                }
-            }
+	my %servers = &get_servers($dom,'library');
+	foreach my $tryserver (keys(%servers)) {
+	    unless (&reply('domroleput:'.$dom.':'.
+			   $domrolebuffer{$dom},$tryserver) eq 'ok') {
+		&logthis('Put of domain roles failed for '.$dom.' and  '.$tryserver);
+	    }
         }
     }
     $dumpcount++;
@@ -1925,7 +2070,7 @@ sub get_course_adv_roles {
 }
 
 sub get_my_roles {
-    my ($uname,$udom)=@_;
+    my ($uname,$udom,$types,$roles,$roledoms)=@_;
     unless (defined($uname)) { $uname=$env{'user.name'}; }
     unless (defined($udom)) { $udom=$env{'user.domain'}; }
     my %dumphash=
@@ -1935,11 +2080,35 @@ sub get_my_roles {
     foreach my $entry (keys(%dumphash)) {
 	my ($tend,$tstart)=split(/\:/,$dumphash{$entry});
         if (($tstart) && ($tstart<0)) { next; }
-        if (($tend) && ($tend<$now)) { next; }
-        if (($tstart) && ($now<$tstart)) { next; }
+        my $status = 'active';
+        if (($tend) && ($tend<$now)) {
+            $status = 'previous';
+        } 
+        if (($tstart) && ($now<$tstart)) {
+            $status = 'future';
+        }
+        if (ref($types) eq 'ARRAY') {
+            if (!grep(/^\Q$status\E$/,@{$types})) {
+                next;
+            } 
+        } else {
+            if ($status ne 'active') {
+                next;
+            }
+        }
         my ($role,$username,$domain,$section)=split(/\:/,$entry);
+        if (ref($roledoms) eq 'ARRAY') {
+            if (!grep(/^\Q$domain\E$/,@{$roledoms})) {
+                next;
+            }
+        }
+        if (ref($roles) eq 'ARRAY') {
+            if (!grep(/^\Q$role\E$/,@{$roles})) {
+                next;
+            }
+        } 
 	$returnhash{$username.':'.$domain.':'.$role}=$tstart.':'.$tend;
-     }
+    }
     return %returnhash;
 }
 
@@ -1949,7 +2118,7 @@ sub get_my_roles {
 
 sub postannounce {
     my ($server,$text)=@_;
-    unless (&allowed('psa',$hostdom{$server})) { return 'refused'; }
+    unless (&allowed('psa',&host_domain($server))) { return 'refused'; }
     unless ($text=~/\w/) { $text=''; }
     return &reply('setannounce:'.&escape($text),$server);
 }
@@ -1985,11 +2154,16 @@ sub courseiddump {
     my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter,$coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok)=@_;
     my %returnhash=();
     unless ($domfilter) { $domfilter=''; }
-    foreach my $tryserver (keys %libserv) {
-        if ( ($hostidflag == 1 && grep/^$tryserver$/,@{$hostidref}) || (!defined($hostidflag)) ) {
-	    if ((!$domfilter) || ($hostdom{$tryserver} eq $domfilter)) {
+    my %libserv = &all_library();
+    foreach my $tryserver (keys(%libserv)) {
+        if ( (  $hostidflag == 1 
+	        && grep(/^\Q$tryserver\E$/,@{$hostidref}) ) 
+	     || (!defined($hostidflag)) ) {
+
+	    if ($domfilter eq ''
+		|| (&host_domain($tryserver) eq $domfilter)) {
 	        foreach my $line (
-                 split(/\&/,&reply('courseiddump:'.$hostdom{$tryserver}.':'.
+                 split(/\&/,&reply('courseiddump:'.&host_domain($tryserver).':'.
 			       $sincefilter.':'.&escape($descfilter).':'.
                                &escape($instcodefilter).':'.&escape($ownerfilter).':'.&escape($coursefilter).':'.&escape($typefilter).':'.&escape($regexp_ok),
                                $tryserver))) {
@@ -2017,12 +2191,13 @@ sub dcmailput {
 sub dcmaildump {
     my ($dom,$startdate,$enddate,$senders) = @_;
     my %returnhash=();
-    if (exists($domain_primary{$dom})) {
+
+    if (defined(&domain($dom,'primary'))) {
         my $cmd='dcmaildump:'.$dom.':'.&escape($startdate).':'.
                                                          &escape($enddate).':';
 	my @esc_senders=map { &escape($_)} @$senders;
 	$cmd.=&escape(join('&',@esc_senders));
-	foreach my $line (split(/\&/,&reply($cmd,$domain_primary{$dom}))) {
+	foreach my $line (split(/\&/,&reply($cmd,&domain($dom,'primary')))) {
             my ($key,$value) = split(/\=/,$line,2);
             if (($key) && ($value)) {
                 $returnhash{&unescape($key)} = &unescape($value);
@@ -2043,19 +2218,19 @@ sub get_domain_roles {
     }
     my $rolelist = join(':',@{$roles});
     my %personnel = ();
-    foreach my $tryserver (keys(%libserv)) {
-        if ($hostdom{$tryserver} eq $dom) {
-            %{$personnel{$tryserver}}=();
-            foreach my $line (
-                split(/\&/,&reply('domrolesdump:'.$dom.':'.
-                   &escape($startdate).':'.&escape($enddate).':'.
-                   &escape($rolelist), $tryserver))) {
-                my ($key,$value) = split(/\=/,$line,2);
-                if (($key) && ($value)) {
-                    $personnel{$tryserver}{&unescape($key)} = &unescape($value);
-                }
-            }
-        }
+
+    my %servers = &get_servers($dom,'library');
+    foreach my $tryserver (keys(%servers)) {
+	%{$personnel{$tryserver}}=();
+	foreach my $line (split(/\&/,&reply('domrolesdump:'.$dom.':'.
+					    &escape($startdate).':'.
+					    &escape($enddate).':'.
+					    &escape($rolelist), $tryserver))) {
+	    my ($key,$value) = split(/\=/,$line,2);
+	    if (($key) && ($value)) {
+		$personnel{$tryserver}{&unescape($key)} = &unescape($value);
+	    }
+	}
     }
     return %personnel;
 }
@@ -2145,7 +2320,7 @@ sub checkin {
     my $now=time;
     my ($ta,$tb,$lonhost)=split(/\*/,$token);
     $lonhost=~tr/A-Z/a-z/;
-    my $dtoken=$ta.'_'.$hostname{$lonhost}.'_'.$tb;
+    my $dtoken=$ta.'_'.&hostname($lonhost).'_'.$tb;
     $dtoken=~s/\W/\_/g;
     my ($dummy,$tuname,$tudom,$tcrsid,$symb,$chtim,$rmaddr)=
                  split(/\&/,&unescape(&reply('tmpget:'.$dtoken,$lonhost)));
@@ -2688,6 +2863,7 @@ sub coursedescription {
     if (!$args->{'one_time'}) {
 	$envhash{'course.'.$normalid.'.last_cache'}=time;
     }
+
     if ($chome ne 'no_host') {
        %returnhash=&dump('environment',$cdomain,$cnum);
        if (!exists($returnhash{'con_lost'})) {
@@ -2763,8 +2939,8 @@ sub rolesinit {
 	    $area=~s/\_\w\w$//;
             my ($trole,$tend,$tstart,$group_privs);
 	    if ($role=~/^cr/) { 
-		if ($role=~m|^(cr/\w+/\w+/[a-zA-Z0-9]+)_(.*)$|) {
-		    ($trole,my $trest)=($role=~m|^(cr/\w+/\w+/[a-zA-Z0-9]+)_(.*)$|);
+		if ($role=~m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|) {
+		    ($trole,my $trest)=($role=~m|^(cr/$match_domain/$match_username/[a-zA-Z0-9]+)_(.*)$|);
 		    ($tend,$tstart)=split('_',$trest);
 		} else {
 		    $trole=$role;
@@ -2813,7 +2989,7 @@ sub custom_roleprivs {
     my ($allroles,$trole,$tdomain,$trest,$spec,$area) = @_;
     my ($rdummy,$rdomain,$rauthor,$rrole)=split(/\//,$trole);
     my $homsvr=homeserver($rauthor,$rdomain);
-    if ($hostname{$homsvr} ne '') {
+    if (&hostname($homsvr) ne '') {
         my ($rdummy,$roledef)=
             &get('roles',["rolesdef_$rrole"],$rdomain,$rauthor);
         if (($rdummy ne 'con_lost') && ($roledef ne '')) {
@@ -2843,7 +3019,7 @@ sub group_roleprivs {
     if (($tend!=0) && ($tend<$now)) { $access = 0; }
     if (($tstart!=0) && ($tstart>$now)) { $access=0; }
     if ($access) {
-        my ($course,$group) = ($area =~ m|(/\w+/\w+)/([^/]+)$|);
+        my ($course,$group) = ($area =~ m|(/$match_domain/$match_courseid)/([^/]+)$|);
         $$allgroups{$course}{$group} .=':'.$group_privs;
     }
 }
@@ -2874,7 +3050,7 @@ sub set_userprivs {
     if (keys(%{$allgroups}) > 0) {
         foreach my $role (keys %{$allroles}) {
             my ($trole,$area,$sec,$extendedarea);
-            if ($role =~ m-^(\w+|cr/\w+/\w+/\w+)\.(/\w+/\w+)(/?\w*)-) {
+            if ($role =~ m-^(\w+|cr/$match_domain/$match_username/\w+)\.(/$match_domain/$match_courseid)(/?\w*)-) {
                 $trole = $1;
                 $area = $2;
                 $sec = $3;
@@ -2986,7 +3162,23 @@ sub dump {
 
 sub dumpstore {
    my ($namespace,$udomain,$uname,$regexp,$range)=@_;
-   return &dump($namespace,$udomain,$uname,$regexp,$range);
+   if (!$udomain) { $udomain=$env{'user.domain'}; }
+   if (!$uname) { $uname=$env{'user.name'}; }
+   my $uhome=&homeserver($uname,$udomain);
+   if ($regexp) {
+       $regexp=&escape($regexp);
+   } else {
+       $regexp='.';
+   }
+   my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome);
+   my @pairs=split(/\&/,$rep);
+   my %returnhash=();
+   foreach my $item (@pairs) {
+       my ($key,$value)=split(/=/,$item,2);
+       next if ($key =~ /^error: 2 /);
+       $returnhash{$key}=&thaw_unescape($value);
+   }
+   return %returnhash;
 }
 
 # -------------------------------------------------------------- keys interface
@@ -2999,6 +3191,7 @@ sub getkeys {
    my $rep=reply("keys:$udomain:$uname:$namespace",$uhome);
    my @keyarray=();
    foreach my $key (split(/\&/,$rep)) {
+      next if ($key =~ /^error: 2 /);
       push(@keyarray,&unescape($key));
    }
    return @keyarray;
@@ -3019,7 +3212,7 @@ sub currentdump {
    if ($rep eq "unknown_cmd") { 
        # an old lond will not know currentdump
        # Do a dump and make it look like a currentdump
-       my @tmp = &dump($courseid,$sdom,$sname,'.');
+       my @tmp = &dumpstore($courseid,$sdom,$sname,'.');
        return if ($tmp[0] =~ /^(error:|no_such_host)/);
        my %hash = @tmp;
        @tmp=();
@@ -3044,6 +3237,8 @@ sub convert_dump_to_currentdump{
     # we might run in to problems with parameter names =~ /^v\./
     while (my ($key,$value) = each(%hash)) {
         my ($v,$symb,$param) = split(/:/,$key);
+	$symb  = &unescape($symb);
+	$param = &unescape($param);
         next if ($v eq 'version' || $symb eq 'keys');
         next if (exists($returnhash{$symb}) &&
                  exists($returnhash{$symb}->{$param}) &&
@@ -3257,6 +3452,22 @@ sub portfolio_access {
     my ($requrl) = @_;
     my (undef,$udom,$unum,$file_name,$group) = &parse_portfolio_url($requrl);
     my $result = &get_portfolio_access($udom,$unum,$file_name,$group);
+    if ($result) {
+        my %setters;
+        if ($env{'user.name'} eq 'public' && $env{'user.domain'} eq 'public') {
+            my ($startblock,$endblock) =
+                &Apache::loncommon::blockcheck(\%setters,'port',$unum,$udom);
+            if ($startblock && $endblock) {
+                return 'B';
+            }
+        } else {
+            my ($startblock,$endblock) =
+                &Apache::loncommon::blockcheck(\%setters,'port');
+            if ($startblock && $endblock) {
+                return 'B';
+            }
+        }
+    }
     if ($result eq 'ok') {
        return 'F';
     } elsif ($result =~ /^[^:]+:guest_/) {
@@ -3332,7 +3543,7 @@ sub get_portfolio_access {
                 my (%allgroups,%allroles); 
                 my ($start,$end,$role,$sec,$group);
                 foreach my $envkey (%env) {
-                    if ($envkey =~ m-^user\.role\.(gr|cc|in|ta|ep|st)\./([^/]+)/([^/]+)/?([^/]*)$-) {
+                    if ($envkey =~ m-^user\.role\.(gr|cc|in|ta|ep|st)\./($match_domain)/($match_courseid)/?([^/]*)$-) {
                         my $cid = $2.'_'.$3; 
                         if ($1 eq 'gr') {
                             $group = $4;
@@ -3345,7 +3556,7 @@ sub get_portfolio_access {
                             }
                             $allroles{$cid}{$1}{$sec} = $env{$envkey};
                         }
-                    } elsif ($envkey =~ m-^user\.role\./cr/(\w+/\w+/\w*)./([^/]+)/([^/]+)/?([^/]*)$-) {
+                    } elsif ($envkey =~ m-^user\.role\./cr/($match_domain/$match_username/\w*)./($match_domain)/($match_courseid)/?([^/]*)$-) {
                         my $cid = $2.'_'.$3;
                         if ($4 eq '') {
                             $sec = 'none';
@@ -3440,12 +3651,12 @@ sub parse_portfolio_url {
 
     my ($type,$udom,$unum,$group,$file_name);
     
-    if ($url =~  m-^/*uploaded/([^/]+)/([^/]+)/portfolio(/.+)$-) {
+    if ($url =~  m-^/*(?:uploaded|editupload)/($match_domain)/($match_username)/portfolio(/.+)$-) {
 	$type = 1;
         $udom = $1;
         $unum = $2;
         $file_name = $3;
-    } elsif ($url =~ m-^/*uploaded/([^/]+)/([^/]+)/groups/([^/]+)/portfolio/(.+)$-) {
+    } elsif ($url =~ m-^/*(?:uploaded|editupload)/($match_domain)/($match_courseid)/groups/([^/]+)/portfolio/(.+)$-) {
 	$type = 2;
         $udom = $1;
         $unum = $2;
@@ -3476,9 +3687,10 @@ sub is_portfolio_file {
 
 sub customaccess {
     my ($priv,$uri)=@_;
-    my ($urole,$urealm)=split(/\./,$env{'request.role'});
-    $urealm=~s/^\W//;
-    my ($udom,$ucrs,$usec)=split(/\//,$urealm);
+    my ($urole,$urealm)=split(/\./,$env{'request.role'},2);
+    my (undef,$udom,$ucrs,$usec)=split(/\//,$urealm);
+    $udom = &LONCAPA::clean_domain($udom);
+    $ucrs = &LONCAPA::clean_username($ucrs);
     my $access=0;
     foreach my $right (split(/\s*\,\s*/,&metadata($uri,'rule_rights'))) {
 	my ($effect,$realm,$role)=split(/\:/,$right);
@@ -3509,12 +3721,21 @@ sub customaccess {
 # ------------------------------------------------- Check for a user privilege
 
 sub allowed {
-    my ($priv,$uri,$symb)=@_;
+    my ($priv,$uri,$symb,$role)=@_;
     my $ver_orguri=$uri;
     $uri=&deversion($uri);
     my $orguri=$uri;
     $uri=&declutter($uri);
-    
+
+    if ($priv eq 'evb') {
+# Evade communication block restrictions for specified role in a course
+        if ($env{'user.priv.'.$role} =~/evb\&([^\:]*)/) {
+            return $1;
+        } else {
+            return;
+        }
+    }
+
     if (defined($env{'allowed.'.$priv})) { return $env{'allowed.'.$priv}; }
 # Free bre access to adm and meta resources
     if (((($uri=~/^adm\//) && ($uri !~ m{/(?:smppg|bulletinboard)$})) 
@@ -3527,7 +3748,14 @@ sub allowed {
     my ($space,$domain,$name,@dir)=split('/',$uri);
     if (($space=~/^(uploaded|editupload)$/) && ($env{'user.name'} eq $name) && 
 	($env{'user.domain'} eq $domain) && ('portfolio' eq $dir[0])) {
-        return 'F';
+        my %setters;
+        my ($startblock,$endblock) = 
+            &Apache::loncommon::blockcheck(\%setters,'port');
+        if ($startblock && $endblock) {
+            return 'B';
+        } else {
+            return 'F';
+        }
     }
 
 # bre access to group portfolio for rgf priv in group, or mdg or vcg in course.
@@ -3803,6 +4031,8 @@ sub allowed {
     unless ($env{'request.course.id'}) {
 	if ($thisallowed eq 'A') {
 	    return 'A';
+        } elsif ($thisallowed eq 'B') {
+            return 'B';
 	} else {
 	    return '1';
 	}
@@ -3870,6 +4100,8 @@ sub allowed {
 
     if ($thisallowed eq 'A') {
 	return 'A';
+    } elsif ($thisallowed eq 'B') {
+        return 'B';
     }
    return 'F';
 }
@@ -3965,6 +4197,7 @@ sub definerole {
 sub metadata_query {
     my ($query,$custom,$customshow,$server_array)=@_;
     my %rhash;
+    my %libserv = &all_library();
     my @server_list = (defined($server_array) ? @$server_array
                                               : keys(%libserv) );
     for my $server (@server_list) {
@@ -3988,7 +4221,7 @@ sub log_query {
     my ($uname,$udom,$query,%filters)=@_;
     my $uhome=&homeserver($uname,$udom);
     if ($uhome eq 'no_host') { return 'error: no_host'; }
-    my $uhost=$hostname{$uhome};
+    my $uhost=&hostname($uhome);
     my $command=&escape(join(':',map{$_.'='.$filters{$_}} keys(%filters)));
     my $queryid=&reply("querysend:".$query.':'.$udom.':'.$uname.':'.$command,
                        $uhome);
@@ -3996,6 +4229,18 @@ sub log_query {
     return get_query_reply($queryid);
 }
 
+# -------------------------- Update MySQL table for portfolio file
+
+sub update_portfolio_table {
+    my ($uname,$udom,$file_name,$query,$group,$action) = @_;
+    my $homeserver = &homeserver($uname,$udom);
+    my $queryid=
+        &reply("querysend:".$query.':'.&escape($uname.':'.$udom.':'.$group).
+               ':'.&escape($file_name).':'.$action,$homeserver);
+    my $reply = &get_query_reply($queryid);
+    return $reply;
+}
+
 # ------- Request retrieval of institutional classlists for course(s)
 
 sub fetch_enrollment_query {
@@ -4008,7 +4253,7 @@ sub fetch_enrollment_query {
     } else {
         $homeserver = &homeserver($cnum,$dom);
     }
-    my $host=$hostname{$homeserver};
+    my $host=&hostname($homeserver);
     my $cmd = '';
     foreach my $affiliate (keys %{$affiliatesref}) {
         $cmd .= $affiliate.'='.join(",",@{$$affiliatesref{$affiliate}}).'%%';
@@ -4199,7 +4444,7 @@ sub auto_photochoice {
 sub auto_photoupdate {
     my ($affiliatesref,$dom,$cnum,$photo) = @_;
     my $homeserver = &homeserver($cnum,$dom);
-    my $host=$hostname{$homeserver};
+    my $host=&hostname($homeserver);
     my $cmd = '';
     my $maxtries = 1;
     foreach my $affiliate (keys(%{$affiliatesref})) {
@@ -4239,12 +4484,11 @@ sub auto_instcode_format {
     my $courses = '';
     my @homeservers;
     if ($caller eq 'global') {
-        foreach my $tryserver (keys(%libserv)) {
-            if ($hostdom{$tryserver} eq $codedom) {
-                if (!grep(/^\Q$tryserver\E$/,@homeservers)) {
-                    push(@homeservers,$tryserver);
-                }
-            }
+	my %servers = &get_servers($codedom,'library');
+	foreach my $tryserver (keys(%servers)) {
+	    if (!grep(/^\Q$tryserver\E$/,@homeservers)) {
+		push(@homeservers,$tryserver);
+	    }
         }
     } else {
         push(@homeservers,&homeserver($caller,$codedom));
@@ -4278,35 +4522,31 @@ sub auto_instcode_format {
 sub auto_instcode_defaults {
     my ($domain,$returnhash,$code_order) = @_;
     my @homeservers;
-    foreach my $tryserver (keys(%libserv)) {
-        if ($hostdom{$tryserver} eq $domain) {
-            if (!grep(/^\Q$tryserver\E$/,@homeservers)) {
-                push(@homeservers,$tryserver);
-            }
-        }
+
+    my %servers = &get_servers($domain,'library');
+    foreach my $tryserver (keys(%servers)) {
+	if (!grep(/^\Q$tryserver\E$/,@homeservers)) {
+	    push(@homeservers,$tryserver);
+	}
     }
-    my $ok_response = 0;
+
     my $response;
-    while (@homeservers > 0 && $ok_response == 0) {
-        my $server = shift(@homeservers);
+    foreach my $server (@homeservers) {
         $response=&reply('autoinstcodedefaults:'.$domain,$server);
-        if ($response !~ /(con_lost|error|no_such_host|refused)/) {
-            foreach my $pair (split(/\&/,$response)) {
-                my ($name,$value)=split(/\=/,$pair);
-                if ($name eq 'code_order') {
-                    @{$code_order} = split(/\&/,&unescape($value));
-                } else {
-                    $returnhash->{&unescape($name)}=&unescape($value);
-                }
-            }
-        }
-        $ok_response = 1;
-    }
-    if ($ok_response) {
-        return 'ok';
-    } else {
-        return $response;
+        next if ($response =~ /(con_lost|error|no_such_host|refused)/);
+	
+	foreach my $pair (split(/\&/,$response)) {
+	    my ($name,$value)=split(/\=/,$pair);
+	    if ($name eq 'code_order') {
+		@{$code_order} = split(/\&/,&unescape($value));
+	    } else {
+		$returnhash->{&unescape($name)}=&unescape($value);
+	    }
+	}
+	return 'ok';
     }
+
+    return $response;
 } 
 
 sub auto_validate_class_sec {
@@ -4320,8 +4560,8 @@ sub auto_validate_class_sec {
 # ------------------------------------------------------- Course Group routines
 
 sub get_coursegroups {
-    my ($cdom,$cnum,$group) = @_;
-    return(&dump('coursegroups',$cdom,$cnum,$group));
+    my ($cdom,$cnum,$group,$namespace) = @_;
+    return(&dump($namespace,$cdom,$cnum,$group));
 }
 
 sub modify_coursegroup {
@@ -4329,6 +4569,37 @@ sub modify_coursegroup {
     return(&put('coursegroups',$groupsettings,$cdom,$cnum));
 }
 
+sub toggle_coursegroup_status {
+    my ($cdom,$cnum,$group,$action) = @_;
+    my ($from_namespace,$to_namespace);
+    if ($action eq 'delete') {
+        $from_namespace = 'coursegroups';
+        $to_namespace = 'deleted_groups';
+    } else {
+        $from_namespace = 'deleted_groups';
+        $to_namespace = 'coursegroups';
+    }
+    my %curr_group = &get_coursegroups($cdom,$cnum,$group,$from_namespace);
+    if (my $tmp = &error(%curr_group)) {
+        &Apache::lonnet::logthis('Error retrieving group: '.$tmp.' in '.$cnum.':'.$cdom);
+        return ('read error',$tmp);
+    } else {
+        my %savedsettings = %curr_group; 
+        my $result = &put($to_namespace,\%savedsettings,$cdom,$cnum);
+        my $deloutcome;
+        if ($result eq 'ok') {
+            $deloutcome = &del($from_namespace,[$group],$cdom,$cnum);
+        } else {
+            return ('write error',$result);
+        }
+        if ($deloutcome eq 'ok') {
+            return 'ok';
+        } else {
+            return ('delete error',$deloutcome);
+        }
+    }
+}
+
 sub modify_group_roles {
     my ($cdom,$cnum,$group_id,$user,$end,$start,$userprivs) = @_;
     my $url = '/'.$cdom.'/'.$cnum.'/'.$group_id;
@@ -4352,7 +4623,7 @@ sub get_active_groups {
     my $now = time;
     my %groups = ();
     foreach my $key (keys(%env)) {
-        if ($key =~ m-user\.role\.gr\./([^/]+)/([^/]+)/(\w+)$-) {
+        if ($key =~ m-user\.role\.gr\./($match_domain)/($match_courseid)/(\w+)$-) {
             my ($start,$end) = split(/\./,$env{$key});
             if (($end!=0) && ($end<$now)) { next; }
             if (($start!=0) && ($start>$now)) { next; }
@@ -4373,8 +4644,6 @@ sub get_users_groups {
     my ($udom,$uname,$courseid) = @_;
     my @usersgroups;
     my $cachetime=1800;
-    $courseid=~s/\_/\//g;
-    $courseid=~s/^(\w)/\/$1/;
 
     my $hashid="$udom:$uname:$courseid";
     my ($grouplist,$cached)=&is_cached_new('getgroups',$hashid);
@@ -4382,38 +4651,34 @@ sub get_users_groups {
         @usersgroups = split(/:/,$grouplist);
     } else {  
         $grouplist = '';
-        my %roleshash = &dump('roles',$udom,$uname,$courseid);
-        my ($tmp) = keys(%roleshash);
-        if ($tmp=~/^error:/) {
-            &logthis('Error retrieving roles: '.$tmp.' for '.$uname.':'.$udom);
-        } else {
-            my $access_end = $env{'course.'.$courseid.
-                                  '.default_enrollment_end_date'};
-            my $now = time;
-            foreach my $key (keys(%roleshash)) {
-                if ($key =~ /^\Q$courseid\E\/(\w+)\_gr$/) {
-                    my $group = $1;
-                    if ($roleshash{$key} =~ /_(\d+)_(\d+)$/) {
-                        my $start = $2;
-                        my $end = $1;
-                        if ($start == -1) { next; } # deleted from group
-                        if (($start!=0) && ($start>$now)) { next; }
-                        if (($end!=0) && ($end<$now)) {
-                            if ($access_end && $access_end < $now) {
-                                if ($access_end - $end < 86400) {
-                                    push(@usersgroups,$group);
-                                }
+        my $courseurl = &courseid_to_courseurl($courseid);
+        my %roleshash = &dump('roles',$udom,$uname,$courseurl);
+        my $access_end = $env{'course.'.$courseid.
+                              '.default_enrollment_end_date'};
+        my $now = time;
+        foreach my $key (keys(%roleshash)) {
+            if ($key =~ /^\Q$courseurl\E\/(\w+)\_gr$/) {
+                my $group = $1;
+                if ($roleshash{$key} =~ /_(\d+)_(\d+)$/) {
+                    my $start = $2;
+                    my $end = $1;
+                    if ($start == -1) { next; } # deleted from group
+                    if (($start!=0) && ($start>$now)) { next; }
+                    if (($end!=0) && ($end<$now)) {
+                        if ($access_end && $access_end < $now) {
+                            if ($access_end - $end < 86400) {
+                                push(@usersgroups,$group);
                             }
-                            next;
                         }
-                        push(@usersgroups,$group);
+                        next;
                     }
+                    push(@usersgroups,$group);
                 }
             }
-            @usersgroups = &sort_course_groups($courseid,@usersgroups);
-            $grouplist = join(':',@usersgroups);
-            &do_cache_new('getgroups',$hashid,$grouplist,$cachetime);
         }
+        @usersgroups = &sort_course_groups($courseid,@usersgroups);
+        $grouplist = join(':',@usersgroups);
+        &do_cache_new('getgroups',$hashid,$grouplist,$cachetime);
     }
     return @usersgroups;
 }
@@ -4421,8 +4686,7 @@ sub get_users_groups {
 sub devalidate_getgroups_cache {
     my ($udom,$uname,$cdom,$cnum)=@_;
     my $courseid = $cdom.'_'.$cnum;
-    $courseid=~s/\_/\//g;
-    $courseid=~s/^(\w)/\/$1/;
+
     my $hashid="$udom:$uname:$courseid";
     &devalidate_cache_new('getgroups',$hashid);
 }
@@ -4461,7 +4725,7 @@ sub assignrole {
     my $mrole;
     if ($role =~ /^cr\//) {
         my $cwosec=$url;
-        $cwosec=~s/^\/(\w+)\/(\w+)\/.*/$1\/$2/;
+        $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/;
 	unless (&allowed('ccr',$cwosec)) {
            &logthis('Refused custom assignrole: '.
              $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '.
@@ -4471,7 +4735,7 @@ sub assignrole {
         $mrole='cr';
     } elsif ($role =~ /^gr\//) {
         my $cwogrp=$url;
-        $cwogrp=~s/^\/(\w+)\/(\w+)\/.*/$1\/$2/;
+        $cwogrp=~s{^/($match_domain)/($match_courseid)/.*}{$1/$2};
         unless (&allowed('mdg',$cwogrp)) {
             &logthis('Refused group assignrole: '.
               $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '.
@@ -4481,7 +4745,7 @@ sub assignrole {
         $mrole='gr';
     } else {
         my $cwosec=$url;
-        $cwosec=~s/^\/(\w+)\/(\w+)\/.*/$1\/$2/;
+        $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/;
         unless ((&allowed('c'.$role,$cwosec)) || &allowed('c'.$role,$udom)) { 
            &logthis('Refused assignrole: '.
              $udom.' '.$uname.' '.$url.' '.$role.' '.$end.' '.$start.' by '.
@@ -4561,8 +4825,8 @@ sub modifyuser {
         $umode,   $upass, $first,
         $middle,  $last,  $gene,
         $forceid, $desiredhome, $email)=@_;
-    $udom=~s/\W//g;
-    $uname=~s/\W//g;
+    $udom= &LONCAPA::clean_domain($udom);
+    $uname=&LONCAPA::clean_username($uname);
     &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '.
              $umode.', '.$first.', '.$middle.', '.
 	     $last.', '.$gene.'(forceid: '.$forceid.')'.
@@ -4575,21 +4839,19 @@ sub modifyuser {
     if (($uhome eq 'no_host') && 
 	(($umode && $upass) || ($umode eq 'localauth'))) {
         my $unhome='';
-        if (defined($desiredhome) && $hostdom{$desiredhome} eq $udom) { 
+        if (defined($desiredhome) && &host_domain($desiredhome) eq $udom) { 
             $unhome = $desiredhome;
 	} elsif($env{'course.'.$env{'request.course.id'}.'.domain'} eq $udom) {
 	    $unhome=$env{'course.'.$env{'request.course.id'}.'.home'};
         } else { # load balancing routine for determining $unhome
-            my $tryserver;
             my $loadm=10000000;
-            foreach $tryserver (keys %libserv) {
-	       if ($hostdom{$tryserver} eq $udom) {
-                  my $answer=reply('load',$tryserver);
-                  if (($answer=~/\d+/) && ($answer<$loadm)) {
-		      $loadm=$answer;
-                      $unhome=$tryserver;
-                  }
-	       }
+	    my %servers = &get_servers($udom,'library');
+	    foreach my $tryserver (keys(%servers)) {
+		my $answer=reply('load',$tryserver);
+		if (($answer=~/\d+/) && ($answer<$loadm)) {
+		    $loadm=$answer;
+		    $unhome=$tryserver;
+		}
 	    }
         }
         if (($unhome eq '') || ($unhome eq 'no_host')) {
@@ -4804,7 +5066,7 @@ sub createcourse {
    }
 # ------------------------------------------------ Check supplied server name
     $course_server = $env{'user.homeserver'} if (! defined($course_server));
-    if (! exists($libserv{$course_server})) {
+    if (! &is_library($course_server)) {
         return 'error:bad server name '.$course_server;
     }
 # ------------------------------------------------------------- Make the course
@@ -4847,6 +5109,16 @@ ENDINITMAP
     return '/'.$udom.'/'.$uname;
 }
 
+sub is_course {
+    my ($cdom,$cnum) = @_;
+    my %courses = &courseiddump($cdom,'.',1,'.','.',$cnum,undef,
+				undef,'.');
+    if (exists($courses{$cdom.'_'.$cnum})) {
+        return 1;
+    }
+    return 0;
+}
+
 # ---------------------------------------------------------- Assign Custom Role
 
 sub assigncustomrole {
@@ -4905,9 +5177,7 @@ sub is_locked {
 
 sub declutter_portfile {
     my ($file) = @_;
-    &logthis("got $file");
-    $file =~ s-^(/portfolio/|portfolio/)-/-;
-    &logthis("ret $file");
+    $file =~ s{^(/portfolio/|portfolio/)}{/};
     return $file;
 }
 
@@ -5125,12 +5395,68 @@ sub modify_access_controls {
         #  remove lock
         my @del_lock = ($file_name."\0".'locked_access_records');
         my $dellockoutcome = &del('file_permissions',\@del_lock,$domain,$user);
+        my ($file,$group);
+        if (&is_course($domain,$user)) {
+            ($group,$file) = split(/\//,$file_name,2);
+        } else {
+            $file = $file_name;
+        }
+        my $sqlresult =
+            &update_portfolio_table($user,$domain,$file,'portfolio_access',
+                                    $group);
     } else {
         $outcome = "error: could not obtain lockfile\n";  
     }
     return ($outcome,$deloutcome,\%new_values,\%translation);
 }
 
+sub make_public_indefinitely {
+    my ($requrl) = @_;
+    my $now = time;
+    my $action = 'activate';
+    my $aclnum = 0;
+    if (&is_portfolio_url($requrl)) {
+        my (undef,$udom,$unum,$file_name,$group) =
+            &parse_portfolio_url($requrl);
+        my $current_perms = &get_portfile_permissions($udom,$unum);
+        my %access_controls = &get_access_controls($current_perms,
+                                                   $group,$file_name);
+        foreach my $key (keys(%{$access_controls{$file_name}})) {
+            my ($num,$scope,$end,$start) = 
+                ($key =~ /^([^:]+):([a-z]+)_(\d*)_?(\d*)$/);
+            if ($scope eq 'public') {
+                if ($start <= $now && $end == 0) {
+                    $action = 'none';
+                } else {
+                    $action = 'update';
+                    $aclnum = $num;
+                }
+                last;
+            }
+        }
+        if ($action eq 'none') {
+             return 'ok';
+        } else {
+            my %changes;
+            my $newend = 0;
+            my $newstart = $now;
+            my $newkey = $aclnum.':public_'.$newend.'_'.$newstart;
+            $changes{$action}{$newkey} = {
+                type => 'public',
+                time => {
+                    start => $newstart,
+                    end   => $newend,
+                },
+            };
+            my ($outcome,$deloutcome,$new_values,$translation) =
+                &modify_access_controls($file_name,\%changes,$udom,$unum);
+            return $outcome;
+        }
+    } else {
+        return 'invalid';
+    }
+}
+
 #------------------------------------------------------Get Marked as Read Only
 
 sub get_marked_as_readonly {
@@ -5273,28 +5599,27 @@ sub dirlist {
             return @listing_results;
         } elsif(!defined($alternateDirectoryRoot)) {
             my %allusers;
-            foreach my $tryserver (keys(%libserv)) {
-                if($hostdom{$tryserver} eq $udom) {
-                    my $listing = &reply('ls2:'.$perlvar{'lonDocRoot'}.'/res/'.
-					 $udom, $tryserver);
-                    my @listing_results;
-                    if ($listing eq 'unknown_cmd') {
-                        $listing = &reply('ls:'.$perlvar{'lonDocRoot'}.'/res/'.
-					  $udom, $tryserver);
-                        @listing_results = split(/:/,$listing);
-                    } else {
-                        @listing_results =
-                            map { &unescape($_); } split(/:/,$listing);
-                    }
-                    if ($listing_results[0] ne 'no_such_dir' && 
-                        $listing_results[0] ne 'empty'       &&
-                        $listing_results[0] ne 'con_lost') {
-                        foreach my $line (@listing_results) {
-                            my ($entry) = split(/&/,$line,2);
-                            $allusers{$entry} = 1;
-                        }
-                    }
-                }
+	    my %servers = &get_servers($udom,'library');
+	    foreach my $tryserver (keys(%servers)) {
+		my $listing = &reply('ls2:'.$perlvar{'lonDocRoot'}.'/res/'.
+				     $udom, $tryserver);
+		my @listing_results;
+		if ($listing eq 'unknown_cmd') {
+		    $listing = &reply('ls:'.$perlvar{'lonDocRoot'}.'/res/'.
+				      $udom, $tryserver);
+		    @listing_results = split(/:/,$listing);
+		} else {
+		    @listing_results =
+			map { &unescape($_); } split(/:/,$listing);
+		}
+		if ($listing_results[0] ne 'no_such_dir' && 
+		    $listing_results[0] ne 'empty'       &&
+		    $listing_results[0] ne 'con_lost') {
+		    foreach my $line (@listing_results) {
+			my ($entry) = split(/&/,$line,2);
+			$allusers{$entry} = 1;
+		    }
+		}
             }
             my $alluserstr='';
             foreach my $user (sort(keys(%allusers))) {
@@ -5306,18 +5631,12 @@ sub dirlist {
             return ('missing user name');
         }
     } elsif(!defined($alternateDirectoryRoot)) {
-        my $tryserver;
-        my %alldom=();
-        foreach $tryserver (keys(%libserv)) {
-            $alldom{$hostdom{$tryserver}}=1;
-        }
-        my $alldomstr='';
-        foreach my $domain (sort(keys(%alldom))) {
-            $alldomstr.=$perlvar{'lonDocRoot'}.'/res/'.$domain.'/&domain:';
-        }
-        $alldomstr=~s/:$//;
-        return split(/:/,$alldomstr);       
-    } else {
+        my @all_domains = sort(&all_domains());
+         foreach my $domain (@all_domains) {
+             $domain = $perlvar{'lonDocRoot'}.'/res/'.$domain.'/&domain';
+         }
+         return @all_domains;
+     } else {
         return ('missing domain');
     }
 }
@@ -5336,8 +5655,8 @@ sub dirlist {
 ##
 sub GetFileTimestamp {
     my ($studentDomain,$studentName,$filename,$root)=@_;
-    $studentDomain=~s/\W//g;
-    $studentName=~s/\W//g;
+    $studentDomain = &LONCAPA::clean_domain($studentDomain);
+    $studentName   = &LONCAPA::clean_username($studentName);
     my $subdir=$studentName.'__';
     $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
     my $proname="$studentDomain/$subdir/$studentName";
@@ -5360,13 +5679,13 @@ sub stat_file {
     my ($udom,$uname,$file,$dir);
     if ($uri =~ m-^/(uploaded|editupload)/-) {
 	($udom,$uname,$file) =
-	    ($uri =~ m-/(?:uploaded|editupload)/?([^/]*)/?([^/]*)/?(.*)-);
+	    ($uri =~ m-/(?:uploaded|editupload)/?($match_domain)/?($match_name)/?(.*)-);
 	$file = 'userfiles/'.$file;
 	$dir = &propath($udom,$uname);
     }
     if ($uri =~ m-^/res/-) {
 	($udom,$uname) = 
-	    ($uri =~ m-/(?:res)/?([^/]*)/?([^/]*)/-);
+	    ($uri =~ m-/(?:res)/?($match_domain)/?($match_username)/-);
 	$file = $uri;
     }
 
@@ -5880,7 +6199,8 @@ sub packages_tab_default {
 	    $do_default=1;
 	} elsif ($pack_type eq 'extension') {
 	    push(@extension,[$package,$pack_type,$pack_part]);
-	} else {
+	} elsif ($pack_part eq $part) {
+	    # only look at packages defaults for packages that this id is
 	    push(@specifics,[$package,$pack_type,$pack_part]);
 	}
     }
@@ -5947,7 +6267,7 @@ sub metadata {
 	(($uri =~ m|^/*adm/|) && 
 	     ($uri !~ m|^adm/includes|) && ($uri !~ m|/bulletinboard$|)) ||
         ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ /^~/) ||
-	($uri =~ m|home/[^/]+/public_html/|)) {
+	($uri =~ m|home/$match_username/public_html/|)) {
 	return undef;
     }
     my $filename=$uri;
@@ -6607,6 +6927,7 @@ sub rndseed {
     if (!$domain) { $domain=$wdomain; }
     if (!$username) { $username=$wusername }
     my $which=&get_rand_alg();
+
     if (defined(&getCODE())) {
 	if ($which eq '64bit5') {
 	    return &rndseed_CODE_64bit5($symb,$courseid,$domain,$username);
@@ -6664,7 +6985,6 @@ sub rndseed_64bit {
 	#&logthis("$symbseed:$nameseed;$domainseed|$courseseed;$namechck:$symbchck");
 	#&logthis("rndseed :$num:$symb");
 	if ($_64bit) { $num1=(($num1<<32)>>32); $num2=(($num2<<32)>>32); }
-	if ($_64bit) { $num1=(($num1<<32)>>32); $num2=(($num2<<32)>>32); }
 	return "$num1,$num2";
     }
 }
@@ -6687,6 +7007,7 @@ sub rndseed_64bit2 {
 	my $num2=$nameseed+$domainseed+$courseseed;
 	#&logthis("$symbseed:$nameseed;$domainseed|$courseseed;$namechck:$symbchck");
 	#&logthis("rndseed :$num:$symb");
+	if ($_64bit) { $num1=(($num1<<32)>>32); $num2=(($num2<<32)>>32); }
 	return "$num1,$num2";
     }
 }
@@ -6801,13 +7122,14 @@ sub setup_random_from_rndseed {
 }
 
 sub latest_receipt_algorithm_id {
-    return 'receipt2';
+    return 'receipt3';
 }
 
 sub recunique {
     my $fucourseid=shift;
     my $unique;
-    if ($env{"course.$fucourseid.receiptalg"} eq 'receipt2') {
+    if ($env{"course.$fucourseid.receiptalg"} eq 'receipt2' ||
+	$env{"course.$fucourseid.receiptalg"} eq 'receipt3' ) {
 	$unique=$env{"course.$fucourseid.internal.encseed"};
     } else {
 	$unique=$perlvar{'lonReceipt'};
@@ -6818,7 +7140,8 @@ sub recunique {
 sub recprefix {
     my $fucourseid=shift;
     my $prefix;
-    if ($env{"course.$fucourseid.receiptalg"} eq 'receipt2') {
+    if ($env{"course.$fucourseid.receiptalg"} eq 'receipt2'||
+	$env{"course.$fucourseid.receiptalg"} eq 'receipt3' ) {
 	$prefix=$env{"course.$fucourseid.internal.encpref"};
     } else {
 	$prefix=$perlvar{'lonHostID'};
@@ -6828,15 +7151,23 @@ sub recprefix {
 
 sub ireceipt {
     my ($funame,$fudom,$fucourseid,$fusymb,$part)=@_;
+
+    my $return =&recprefix($fucourseid).'-';
+
+    if ($env{"course.$fucourseid.receiptalg"} eq 'receipt3' ||
+	$env{'request.state'} eq 'construct') {
+	$return .= (&digest("$funame,$fudom,$fucourseid,$fusymb,$part")%10000);
+	return $return;
+    }
+
     my $cuname=unpack("%32C*",$funame);
     my $cudom=unpack("%32C*",$fudom);
     my $cucourseid=unpack("%32C*",$fucourseid);
     my $cusymb=unpack("%32C*",$fusymb);
     my $cunique=&recunique($fucourseid);
     my $cpart=unpack("%32S*",$part);
-    my $return =&recprefix($fucourseid).'-';
-    if ($env{"course.$fucourseid.receiptalg"} eq 'receipt2' ||
-	$env{'request.state'} eq 'construct') {
+    if ($env{"course.$fucourseid.receiptalg"} eq 'receipt2') {
+
 	#&logthis("doing receipt2  using parts $cpart, uname $cuname and udom $cudom gets  ".($cpart%$cuname)." and ".($cpart%$cudom));
 			       
 	$return.= ($cunique%$cuname+
@@ -6924,61 +7255,60 @@ sub repcopy_userfile {
     if ($file =~ m -^/*(uploaded|editupload)/-) { $file=&filelocation("",$file); }
     if ($file =~ m|^/home/httpd/html/lonUsers/|) { return 'ok'; }
     my ($cdom,$cnum,$filename) = 
-	($file=~m|^\Q$perlvar{'lonDocRoot'}\E/+userfiles/+([^/]+)/+([^/]+)/+(.*)|);
-    my ($info,$rtncode);
+	($file=~m|^\Q$perlvar{'lonDocRoot'}\E/+userfiles/+($match_domain)/+($match_name)/+(.*)|);
     my $uri="/uploaded/$cdom/$cnum/$filename";
     if (-e "$file") {
+# we already have a local copy, check it out
 	my @fileinfo = stat($file);
+	my $rtncode;
+	my $info;
 	my $lwpresp = &getuploaded('HEAD',$uri,$cdom,$cnum,\$info,\$rtncode);
 	if ($lwpresp ne 'ok') {
+# there is no such file anymore, even though we had a local copy
 	    if ($rtncode eq '404') {
 		unlink($file);
 	    }
-	    #my $ua=new LWP::UserAgent;
-	    #my $request=new HTTP::Request('GET',&tokenwrapper($uri));
-	    #my $response=$ua->request($request);
-	    #if ($response->is_success()) {
-	#	return $response->content;
-	#    } else {
-	#	return -1;
-	#    }
 	    return -1;
 	}
 	if ($info < $fileinfo[9]) {
+# nice, the file we have is up-to-date, just say okay
 	    return 'ok';
+	} else {
+# the file is outdated, get rid of it
+	    unlink($file);
 	}
-	$info = '';
-	$lwpresp = &getuploaded('GET',$uri,$cdom,$cnum,\$info,\$rtncode);
-	if ($lwpresp ne 'ok') {
-	    return -1;
-	}
-    } else {
-	my $lwpresp = &getuploaded('GET',$uri,$cdom,$cnum,\$info,\$rtncode);
-	if ($lwpresp ne 'ok') {
-	    my $ua=new LWP::UserAgent;
-	    my $request=new HTTP::Request('GET',&tokenwrapper($uri));
-	    my $response=$ua->request($request);
-	    if ($response->is_success()) {
-		$info=$response->content;
-	    } else {
-		return -1;
-	    }
-	}
-	my @parts = ($cdom,$cnum); 
-	if ($filename =~ m|^(.+)/[^/]+$|) {
-	    push @parts, split(/\//,$1);
-	}
-	my $path = $perlvar{'lonDocRoot'}.'/userfiles';
-	foreach my $part (@parts) {
-	    $path .= '/'.$part;
-	    if (!-e $path) {
-		mkdir($path,0770);
-	    }
+    }
+# one way or the other, at this point, we don't have the file
+# construct the correct path for the file
+    my @parts = ($cdom,$cnum); 
+    if ($filename =~ m|^(.+)/[^/]+$|) {
+	push @parts, split(/\//,$1);
+    }
+    my $path = $perlvar{'lonDocRoot'}.'/userfiles';
+    foreach my $part (@parts) {
+	$path .= '/'.$part;
+	if (!-e $path) {
+	    mkdir($path,0770);
 	}
     }
-    open(FILE,">$file");
-    print FILE $info;
-    close(FILE);
+# 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'; }
+    my $request;
+    $uri=~s/^\///;
+    $request=new HTTP::Request('GET','http://'.&hostname(&homeserver($cnum,$cdom)).'/raw/'.$uri);
+    my $response=$ua->request($request,$transferfile);
+# did it work?
+    if ($response->is_error()) {
+	unlink($transferfile);
+	&logthis("Userfile repcopy failed for $uri");
+	return -1;
+    }
+# worked, rename the transfer file
+    rename($transferfile,$file);
     return 'ok';
 }
 
@@ -6992,7 +7322,7 @@ sub tokenwrapper {
     if ($udom && $uname && $file) {
 	$file=~s|(\?\.*)*$||;
         &appenv("userfile.$udom/$uname/$file" => $env{'request.course.id'});
-        return 'http://'.$hostname{ &homeserver($uname,$udom)}.'/'.$uri.
+        return 'http://'.&hostname(&homeserver($uname,$udom)).'/'.$uri.
                (($uri=~/\?/)?'&':'?').'token='.$token.
                                '&tokenissued='.$perlvar{'lonHostID'};
     } else {
@@ -7000,10 +7330,14 @@ sub tokenwrapper {
     }
 }
 
+# call with reqtype HEAD: get last modification time
+# call with reqtype GET: get the file contents
+# Do not call this with reqtype GET for large files! It loads everything into memory
+#
 sub getuploaded {
     my ($reqtype,$uri,$cdom,$cnum,$info,$rtncode) = @_;
     $uri=~s/^\///;
-    $uri = 'http://'.$hostname{ &homeserver($cnum,$cdom)}.'/raw/'.$uri;
+    $uri = 'http://'.&hostname(&homeserver($cnum,$cdom)).'/raw/'.$uri;
     my $ua=new LWP::UserAgent;
     my $request=new HTTP::Request($reqtype,$uri);
     my $response=$ua->request($request);
@@ -7041,12 +7375,12 @@ sub filelocation {
     if ($file=~m:^/~:) { # is a contruction space reference
         $location = $file;
         $location =~ s:/~(.*?)/(.*):/home/$1/public_html/$2:;
-    } elsif ($file=~m:^/home/[^/]*/public_html/:) {
+    } elsif ($file=~m{^/home/$match_username/public_html/}) {
 	# is a correct contruction space reference
         $location = $file;
     } elsif ($file=~/^\/*(uploaded|editupload)/) { # is an uploaded file
         my ($udom,$uname,$filename)=
-  	    ($file=~m -^/+(?:uploaded|editupload)/+([^/]+)/+([^/]+)/+(.*)$-);
+  	    ($file=~m -^/+(?:uploaded|editupload)/+($match_domain)/+($match_name)/+(.*)$-);
         my $home=&homeserver($uname,$udom);
         my $is_me=0;
         my @ids=&current_machine_ids();
@@ -7083,30 +7417,41 @@ sub hreflocation {
     }
     if ($file=~m-^\Q$perlvar{'lonDocRoot'}\E-) {
 	$file=~s-^\Q$perlvar{'lonDocRoot'}\E--;
-    } elsif ($file=~m-/home/(\w+)/public_html/-) {
-	$file=~s-^/home/(\w+)/public_html/-/~$1/-;
+    } elsif ($file=~m-/home/($match_username)/public_html/-) {
+	$file=~s-^/home/($match_username)/public_html/-/~$1/-;
     } elsif ($file=~m-^\Q$perlvar{'lonUsersDir'}\E-) {
-	$file=~s-^/home/httpd/lonUsers/([^/]*)/./././([^/]*)/userfiles/
+	$file=~s-^/home/httpd/lonUsers/($match_domain)/./././($match_name)/userfiles/
 	    -/uploaded/$1/$2/-x;
     }
     return $file;
 }
 
 sub current_machine_domains {
-    my $hostname=$hostname{$perlvar{'lonHostID'}};
+    return &machine_domains(&hostname($perlvar{'lonHostID'}));
+}
+
+sub machine_domains {
+    my ($hostname) = @_;
     my @domains;
+    my %hostname = &all_hostnames();
     while( my($id, $name) = each(%hostname)) {
 #	&logthis("-$id-$name-$hostname-");
 	if ($hostname eq $name) {
-	    push(@domains,$hostdom{$id});
+	    push(@domains,&host_domain($id));
 	}
     }
     return @domains;
 }
 
 sub current_machine_ids {
-    my $hostname=$hostname{$perlvar{'lonHostID'}};
+    return &machine_ids(&hostname($perlvar{'lonHostID'}));
+}
+
+sub machine_ids {
+    my ($hostname) = @_;
+    $hostname ||= &hostname($perlvar{'lonHostID'});
     my @ids;
+    my %hostname = &all_hostnames();
     while( my($id, $name) = each(%hostname)) {
 #	&logthis("-$id-$name-$hostname-");
 	if ($hostname eq $name) {
@@ -7116,6 +7461,29 @@ sub current_machine_ids {
     return @ids;
 }
 
+sub additional_machine_domains {
+    my @domains;
+    open(my $fh,"<$perlvar{'lonTabDir'}/expected_domains.tab");
+    while( my $line = <$fh>) {
+        $line =~ s/\s//g;
+        push(@domains,$line);
+    }
+    return @domains;
+}
+
+sub default_login_domain {
+    my $domain = $perlvar{'lonDefDomain'};
+    my $testdomain=(split(/\./,$ENV{'HTTP_HOST'}))[0];
+    foreach my $posdom (&current_machine_domains(),
+                        &additional_machine_domains()) {
+        if (lc($posdom) eq lc($testdomain)) {
+            $domain=$posdom;
+            last;
+        }
+    }
+    return $domain;
+}
+
 # ------------------------------------------------------------- Declutters URLs
 
 sub declutter {
@@ -7229,76 +7597,202 @@ BEGIN {
     %perlvar = (%perlvar,%{$configvars});
 }
 
+sub get_dns {
+    my ($url,$func) = @_;
+    open(my $config,"<$perlvar{'lonTabDir'}/hosts.tab");
+    foreach my $dns (<$config>) {
+	next if ($dns !~ /^\^(\S*)/x);
+	$dns = $1;
+	my $ua=new LWP::UserAgent;
+	my $request=new HTTP::Request('GET',"http://$dns$url");
+	my $response=$ua->request($request);
+	next if ($response->is_error());
+	my @content = split("\n",$response->content);
+	&$func(\@content);
+    }
+    close($config);
+}
 # ------------------------------------------------------------ Read domain file
 {
-    %domaindescription = ();
-    %domain_auth_def = ();
-    %domain_auth_arg_def = ();
-    my $fh;
-    if (open($fh,"<".$Apache::lonnet::perlvar{'lonTabDir'}.'/domain.tab')) {
-	while (my $line = <$fh>) {
-           next if ($line =~ /^(\#|\s*$)/);
-#           next if /^\#/;
-           chomp $line;
-           my ($domain, $domain_description, $def_auth, $def_auth_arg,
-	       $def_lang, $city, $longi, $lati, $primary) = split(/:/,$line,9);
-	   $domain_auth_def{$domain}=$def_auth;
-           $domain_auth_arg_def{$domain}=$def_auth_arg;
-	   $domaindescription{$domain}=$domain_description;
-	   $domain_lang_def{$domain}=$def_lang;
-	   $domain_city{$domain}=$city;
-	   $domain_longi{$domain}=$longi;
-	   $domain_lati{$domain}=$lati;
-           $domain_primary{$domain}=$primary;
+    my $loaded;
+    my %domain;
+
+    sub parse_domain_tab {
+	my ($lines) = @_;
+	foreach my $line (@$lines) {
+	    next if ($line =~ /^(\#|\s*$ )/x);
+
+	    chomp($line);
+	    my ($name,@elements) = split(/:/,$line,9);
+	    my %this_domain;
+	    foreach my $field ('description', 'auth_def', 'auth_arg_def',
+			       'lang_def', 'city', 'longi', 'lati',
+			       'primary') {
+		$this_domain{$field} = shift(@elements);
+	    }
+	    $domain{$name} = \%this_domain;
+	    &logthis("Domain.tab: $name ".$domain{$name}{'description'} );
+	}
+    }
+    
+    sub load_domain_tab {
+	&get_dns('/adm/dns/domain',\&parse_domain_tab);
+	my $fh;
+	if (open($fh,"<".$perlvar{'lonTabDir'}.'/domain.tab')) {
+	    my @lines = <$fh>;
+	    &parse_domain_tab(\@lines);
+	}
+	close($fh);
+	$loaded = 1;
+    }
+
+    sub domain {
+	&load_domain_tab() if (!$loaded);
+
+	my ($name,$what) = @_;
+	return if ( !exists($domain{$name}) );
 
- #         &logthis("Domain.tab: $domain, $domain_auth_def{$domain}, $domain_auth_arg_def{$domain},$domaindescription{$domain}");
-#          &logthis("Domain.tab: $domain ".$domaindescription{$domain} );
+	if (!$what) {
+	    return $domain{$name}{'description'};
 	}
+	return $domain{$name}{$what};
     }
-    close ($fh);
 }
 
 
 # ------------------------------------------------------------- Read hosts file
 {
-    open(my $config,"<$perlvar{'lonTabDir'}/hosts.tab");
+    my %hostname;
+    my %hostdom;
+    my %libserv;
+    my $loaded;
+
+    sub parse_hosts_tab {
+	my ($file) = @_;
+	foreach my $configline (@$file) {
+	    next if ($configline =~ /^(\#|\s*$ )/x);
+	    next if ($configline =~ /^\^/);
+	    chomp($configline);
+	    my ($id,$domain,$role,$name)=split(/:/,$configline);
+	    $name=~s/\s//g;
+	    if ($id && $domain && $role && $name) {
+		$hostname{$id}=$name;
+		$hostdom{$id}=$domain;
+		if ($role eq 'library') { $libserv{$id}=$name; }
+	    }
+	    &logthis("Hosts.tab: $name ".$id );
+	}
+    }
 
-    while (my $configline=<$config>) {
-       next if ($configline =~ /^(\#|\s*$)/);
-       chomp($configline);
-       my ($id,$domain,$role,$name)=split(/:/,$configline);
-       $name=~s/\s//g;
-       if ($id && $domain && $role && $name) {
-	 $hostname{$id}=$name;
-	 $hostdom{$id}=$domain;
-	 if ($role eq 'library') { $libserv{$id}=$name; }
-       }
+    sub load_hosts_tab {
+	&get_dns('/adm/dns/hosts',\&parse_hosts_tab);
+	open(my $config,"<$perlvar{'lonTabDir'}/hosts.tab");
+	my @config = <$config>;
+	&parse_hosts_tab(\@config);
+	close($config);
+	$loaded=1;
     }
-    close($config);
+
     # FIXME: dev server don't want this, production servers _do_ want this
     #&get_iphost();
-}
 
-sub get_iphost {
-    if (%iphost) { return %iphost; }
-    my %name_to_ip;
-    foreach my $id (keys(%hostname)) {
-	my $name=$hostname{$id};
-	my $ip;
-	if (!exists($name_to_ip{$name})) {
-	    $ip = gethostbyname($name);
-	    if (!$ip || length($ip) ne 4) {
-		&logthis("Skipping host $id name $name no IP found\n");
-		next;
+    sub hostname {
+	&load_hosts_tab() if (!$loaded);
+
+	my ($lonid) = @_;
+	return $hostname{$lonid};
+    }
+
+    sub all_hostnames {
+	&load_hosts_tab() if (!$loaded);
+
+	return %hostname;
+    }
+
+    sub is_library {
+	&load_hosts_tab() if (!$loaded);
+
+	return exists($libserv{$_[0]});
+    }
+
+    sub all_library {
+	&load_hosts_tab() if (!$loaded);
+
+	return %libserv;
+    }
+
+    sub get_servers {
+	&load_hosts_tab() if (!$loaded);
+
+	my ($domain,$type) = @_;
+	my %possible_hosts = ($type eq 'library') ? %libserv
+	                                          : %hostname;
+	my %result;
+	if (ref($domain) eq 'ARRAY') {
+	    while ( my ($host,$hostname) = each(%possible_hosts)) {
+		if (grep(/^\Q$hostdom{$host}\E$/,@$domain)) {
+		    $result{$host} = $hostname;
+		}
 	    }
-	    $ip=inet_ntoa($ip);
-	    $name_to_ip{$name} = $ip;
 	} else {
-	    $ip = $name_to_ip{$name};
+	    while ( my ($host,$hostname) = each(%possible_hosts)) {
+		if ($hostdom{$host} eq $domain) {
+		    $result{$host} = $hostname;
+		}
+	    }
+	}
+	return %result;
+    }
+
+    sub host_domain {
+	&load_hosts_tab() if (!$loaded);
+
+	my ($lonid) = @_;
+	return $hostdom{$lonid};
+    }
+
+    sub all_domains {
+	&load_hosts_tab() if (!$loaded);
+
+	my %seen;
+	my @uniq = grep(!$seen{$_}++, values(%hostdom));
+	return @uniq;
+    }
+}
+
+{ 
+    my %iphost;
+    sub get_hosts_from_ip {
+	my ($ip) = @_;
+	my %iphosts = &get_iphost();
+	if (ref($iphosts{$ip})) {
+	    return @{$iphosts{$ip}};
+	}
+	return;
+    }
+    
+    sub get_iphost {
+	if (%iphost) { return %iphost; }
+	my %name_to_ip;
+	my %hostname = &all_hostnames();
+	foreach my $id (keys(%hostname)) {
+	    my $name=$hostname{$id};
+	    my $ip;
+	    if (!exists($name_to_ip{$name})) {
+		$ip = gethostbyname($name);
+		if (!$ip || length($ip) ne 4) {
+		    &logthis("Skipping host $id name $name no IP found");
+		    next;
+		}
+		$ip=inet_ntoa($ip);
+		$name_to_ip{$name} = $ip;
+	    } else {
+		$ip = $name_to_ip{$name};
+	    }
+	    push(@{$iphost{$ip}},$id);
 	}
-	push(@{$iphost{$ip}},$id);
+	return %iphost;
     }
-    return %iphost;
 }
 
 # ------------------------------------------------------ Read spare server file
@@ -7640,8 +8134,7 @@ passed in @what from the requested user'
 
 =item *
 
-allowed($priv,$uri) : check for a user privilege; returns codes for allowed
-actions
+allowed($priv,$uri,$symb,$role) : check for a user privilege; returns codes for allowed actions
  F: full access
  U,I,K: authentication modes (cxx only)
  '': forbidden
@@ -7660,6 +8153,19 @@ and course level
 plaintext($short) : return value in %prp hash (rolesplain.tab); plain text
 explanation of a user role term
 
+=item *
+
+get_my_roles($uname,$udom,$types,$roles,$roledoms) : All arguments are
+optional.  Returns a hash of a user's roles, with keys set to
+colon-sparated $uname,$udom,and $role, and value set to
+colon-separated start and end times for the role. If no username and
+domain are specified, will default to current user/domain. Types,
+roles, and roledoms are references to arrays, of role statuses
+(active, future or previous), roles (e.g., cc,in, st etc.) and domains
+of the roles which can be used to restrict the list if roles
+reported. If no array ref is provided for types, will default to
+return only active roles.
+
 =back
 
 =head2 User Modification
@@ -8081,6 +8587,15 @@ reference filled in from namesp (encrypt
 log($udom,$name,$home,$message) : write to permanent log for user; use
 critical subroutine
 
+=item *
+
+get_dom($namespace,$storearr,$udomain) : returns hash with keys from array
+reference filled in from namespace found in domain level on primary domain server ($udomain is optional)
+
+=item *
+
+put_dom($namespace,$storehash,$udomain) :  stores hash in namespace at domain level on primary domain server ($udomain is optional)
+
 =back
 
 =head2 Network Status Functions