--- loncom/lonnet/perl/lonnet.pm	2010/07/17 20:02:13	1.1073
+++ loncom/lonnet/perl/lonnet.pm	2011/02/24 23:55:58	1.1104
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1073 2010/07/17 20:02:13 raeburn Exp $
+# $Id: lonnet.pm,v 1.1104 2011/02/24 23:55:58 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -76,7 +76,7 @@ use HTTP::Date;
 use Image::Magick;
 
 use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir
-            $_64bit %env %protocol %loncaparevs);
+            $_64bit %env %protocol %loncaparevs %serverhomeIDs %needsrelease);
 
 my (%badServerCache, $memcache, %courselogs, %accesshash, %domainrolehash,
     %userrolehash, $processmarker, $dumpcount, %coursedombuf,
@@ -95,6 +95,7 @@ use Math::Random;
 use File::MMagic;
 use LONCAPA qw(:DEFAULT :match);
 use LONCAPA::Configuration;
+use File::Copy;
 
 my $readit;
 my $max_connection_retries = 10;     # Or some such value.
@@ -222,7 +223,7 @@ sub get_server_loncaparev {
         my @ids=&current_machine_ids();
         if (grep(/^\Q$lonhost\E$/,@ids)) {
             $answer = $perlvar{'lonVersion'};
-            if ($answer =~ /^[\'\"]?([\d.\-]+)[\'\"]?$/) {
+            if ($answer =~ /^[\'\"]?([\w.\-]+)[\'\"]?$/) {
                 $loncaparev = $1;
             }
         } else {
@@ -230,7 +231,7 @@ sub get_server_loncaparev {
             if (($answer eq 'unknown_cmd') || ($answer eq 'con_lost')) {
                 if ($caller eq 'loncron') {
                     my $ua=new LWP::UserAgent;
-                    $ua->timeout(20);
+                    $ua->timeout(4);
                     my $protocol = $protocol{$lonhost};
                     $protocol = 'http' if ($protocol ne 'https');
                     my $url = $protocol.'://'.&hostname($lonhost).'/adm/about.html';
@@ -238,14 +239,14 @@ sub get_server_loncaparev {
                     my $response=$ua->request($request);
                     unless ($response->is_error()) {
                         my $content = $response->content;
-                        if ($content =~ /<p>VERSION\:\s*([\d.\-]+)<\/p>/) {
+                        if ($content =~ /<p>VERSION\:\s*([\w.\-]+)<\/p>/) {
                             $loncaparev = $1;
                         }
                     }
                 } else {
                     $loncaparev = $loncaparevs{$lonhost};
                 }
-            } elsif ($answer =~ /^[\'\"]?([\d.\-]+)[\'\"]?$/) {
+            } elsif ($answer =~ /^[\'\"]?([\w.\-]+)[\'\"]?$/) {
                 $loncaparev = $1;
             }
         }
@@ -253,6 +254,34 @@ sub get_server_loncaparev {
     }
 }
 
+sub get_server_homeID {
+    my ($hostname,$ignore_cache,$caller) = @_;
+    unless ($ignore_cache) {
+        my ($serverhomeID,$cached)=&is_cached_new('serverhomeID',$hostname);
+        if (defined($cached)) {
+            return $serverhomeID;
+        }
+    }
+    my $cachetime = 12*3600;
+    my $serverhomeID;
+    if ($caller eq 'loncron') { 
+        my @machine_ids = &machine_ids($hostname);
+        foreach my $id (@machine_ids) {
+            my $response = &reply('serverhomeID',$id);
+            unless (($response eq 'unknown_cmd') || ($response eq 'con_lost')) {
+                $serverhomeID = $response;
+                last;
+            }
+        }
+        if ($serverhomeID eq '') {
+            $serverhomeID = $machine_ids[-1];
+        }
+    } else {
+        $serverhomeID = $serverhomeIDs{$hostname};
+    }
+    return &do_cache_new('serverhomeID',$hostname,$serverhomeID,$cachetime);
+}
+
 # -------------------------------------------------- Non-critical communication
 sub subreply {
     my ($cmd,$server)=@_;
@@ -583,11 +612,20 @@ sub appenv {
 # ----------------------------------------------------- Delete from Environment
 
 sub delenv {
-    my ($delthis,$regexp) = @_;
-    if (($delthis=~/user\.role/) || ($delthis=~/user\.priv/)) {
-        &logthis("<font color=\"blue\">WARNING: ".
-                "Attempt to delete from environment ".$delthis);
-        return 'error';
+    my ($delthis,$regexp,$roles) = @_;
+    if (($delthis=~/^user\.role/) || ($delthis=~/^user\.priv/)) {
+        my $refused = 1;
+        if (ref($roles) eq 'ARRAY') {
+            my ($type,$role) = ($delthis =~ /^user\.(role|priv)\.([^.]+)\./);
+            if (grep(/^\Q$role\E$/,@{$roles})) {
+                $refused = 0;
+            }
+        }
+        if ($refused) {
+            &logthis("<font color=\"blue\">WARNING: ".
+                     "Attempt to delete from environment ".$delthis);
+            return 'error';
+        }
     }
     my $opened = open(my $env_file,'+<',$env{'user.environment'});
     if ($opened
@@ -699,13 +737,23 @@ sub userload {
 # ------------------------------ Find server with least workload from spare.tab
 
 sub spareserver {
-    my ($loadpercent,$userloadpercent,$want_server_name) = @_;
+    my ($loadpercent,$userloadpercent,$want_server_name,$udom) = @_;
     my $spare_server;
     if ($userloadpercent !~ /\d/) { $userloadpercent=0; }
     my $lowest_load=($loadpercent > $userloadpercent) ? $loadpercent 
                                                      :  $userloadpercent;
-    
+    my ($uint_dom,$remotesessions);
+    if (($udom ne '') && (&domain($udom) ne '')) {
+        my $uprimary_id = &Apache::lonnet::domain($udom,'primary');
+        $uint_dom = &Apache::lonnet::internet_dom($uprimary_id);
+        my %udomdefaults = &Apache::lonnet::get_domain_defaults($udom);
+        $remotesessions = $udomdefaults{'remotesessions'};
+    }
     foreach my $try_server (@{ $spareid{'primary'} }) {
+        if ($uint_dom) {
+             next unless (&spare_can_host($udom,$uint_dom,$remotesessions,
+                                          $try_server));
+        }
 	($spare_server, $lowest_load) =
 	    &compare_server_load($try_server, $spare_server, $lowest_load);
     }
@@ -714,6 +762,10 @@ sub spareserver {
 
     if (!$found_server) {
 	foreach my $try_server (@{ $spareid{'default'} }) {
+            if ($uint_dom) {
+                next unless (&spare_can_host($udom,$uint_dom,$remotesessions,
+                                             $try_server));
+            }
 	    ($spare_server, $lowest_load) =
 		&compare_server_load($try_server, $spare_server, $lowest_load);
 	}
@@ -726,7 +778,7 @@ sub spareserver {
         }
         if (defined($spare_server)) {
             my $hostname = &hostname($spare_server);
-            if (defined($hostname)) {  
+            if (defined($hostname)) {
 	        $spare_server = $protocol.'://'.$hostname;
             }
         }
@@ -784,6 +836,27 @@ sub has_user_session {
     return 0;
 }
 
+# --------- determine least loaded server in a user's domain which allows login
+
+sub choose_server {
+    my ($udom) = @_;
+    my %domconfhash = &Apache::loncommon::get_domainconf($udom);
+    my %servers = &get_servers($udom);
+    my $lowest_load = 30000;
+    my ($login_host,$hostname);
+    foreach my $lonhost (keys(%servers)) {
+        my $loginvia = $domconfhash{$udom.'.login.loginvia_'.$lonhost};
+        if ($loginvia eq '') {
+            ($login_host, $lowest_load) =
+            &compare_server_load($lonhost, $login_host, $lowest_load);
+        }
+    }
+    if ($login_host ne '') {
+        $hostname = $servers{$login_host};
+    }
+    return ($login_host,$hostname);
+}
+
 # --------------------------------------------- Try to change a user's password
 
 sub changepass {
@@ -884,18 +957,19 @@ sub authenticate {
 }
 
 sub can_host_session {
-    my ($udom,$machinedom,$remoterev,$remotesessions,$hostedsessions) = @_;
+    my ($udom,$lonhost,$remoterev,$remotesessions,$hostedsessions) = @_;
     my $canhost = 1;
+    my $host_idn = &Apache::lonnet::internet_dom($lonhost);
     if (ref($remotesessions) eq 'HASH') {
         if (ref($remotesessions->{'excludedomain'}) eq 'ARRAY') {
-            if (grep(/^\Q$machinedom\E$/,@{$remotesessions->{'excludedomain'}})) {
+            if (grep(/^\Q$host_idn\E$/,@{$remotesessions->{'excludedomain'}})) {
                 $canhost = 0;
             } else {
                 $canhost = 1;
             }
         }
         if (ref($remotesessions->{'includedomain'}) eq 'ARRAY') {
-            if (grep(/^\Q$machinedom\E$/,@{$remotesessions->{'includedomain'}})) {
+            if (grep(/^\Q$host_idn\E$/,@{$remotesessions->{'includedomain'}})) {
                 $canhost = 1;
             } else {
                 $canhost = 0;
@@ -940,6 +1014,26 @@ sub can_host_session {
     return $canhost;
 }
 
+sub spare_can_host {
+    my ($udom,$uint_dom,$remotesessions,$try_server)=@_;
+    my $canhost=1;
+    my @intdoms;
+    my $internet_names = &Apache::lonnet::get_internet_names($try_server);
+    if (ref($internet_names) eq 'ARRAY') {
+        @intdoms = @{$internet_names};
+    }
+    unless (grep(/^\Q$uint_dom\E$/,@intdoms)) {
+        my $serverhomeID = &Apache::lonnet::get_server_homeID($try_server);
+        my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
+        my %defdomdefaults = &Apache::lonnet::get_domain_defaults($serverhomedom);
+        my $remoterev = &Apache::lonnet::get_server_loncaparev(undef,$try_server);
+        $canhost = &can_host_session($udom,$try_server,$remoterev,
+                                     $remotesessions,
+                                     $defdomdefaults{'hostedsessions'});
+    }
+    return $canhost;
+}
+
 # ---------------------- Find the homebase for a user from domain's lib servers
 
 my %homecache;
@@ -1649,7 +1743,8 @@ sub getsection {
     # If there is a role which has expired, return it.
     #
     $courseid = &courseid_to_courseurl($courseid);
-    my %roleshash = &dump('roles',$udom,$unam,$courseid);
+    my $extra = &freeze_escape({'skipcheck' => 1});
+    my %roleshash = &dump('roles',$udom,$unam,$courseid,undef,$extra);
     foreach my $key (keys(%roleshash)) {
         next if ($key !~/^\Q$courseid\E(?:\/)*(\w+)*\_st$/);
         my $section=$1;
@@ -2090,6 +2185,8 @@ sub allowuploaded {
 #        path to file, source of file, instruction to parse file for objects,
 #        ref to hash for embedded objects,
 #        ref to hash for codebase of java objects.
+#        reference to scalar to accommodate mime type determined
+#          from File::MMagic if $parser = parse.
 #
 # output: url to file (if action was uploaddoc), 
 #         ok if successful, or diagnostic message otherwise (if action was propagate or copy)
@@ -2116,7 +2213,8 @@ sub allowuploaded {
 #
 
 sub process_coursefile {
-    my ($action,$docuname,$docudom,$file,$source,$parser,$allfiles,$codebase)=@_;
+    my ($action,$docuname,$docudom,$file,$source,$parser,$allfiles,$codebase,
+        $mimetype)=@_;
     my $fetchresult;
     my $home=&homeserver($docuname,$docudom);
     if ($action eq 'propagate') {
@@ -2144,13 +2242,16 @@ sub process_coursefile {
             close($fh);
             if ($parser eq 'parse') {
                 my $mm = new File::MMagic;
-                my $mime_type = $mm->checktype_filename($filepath.'/'.$fname);
-                if ($mime_type eq 'text/html') {
+                my $type = $mm->checktype_filename($filepath.'/'.$fname);
+                if ($type eq 'text/html') {
                     my $parse_result = &extract_embedded_items($filepath.'/'.$fname,$allfiles,$codebase);
                     unless ($parse_result eq 'ok') {
                         &logthis('Failed to parse '.$filepath.'/'.$fname.' for embedded media: '.$parse_result);
                     }
                 }
+                if (ref($mimetype)) {
+                    $$mimetype = $type;
+                } 
             }
             $fetchresult= &reply('fetchuserfile:'.$docudom.'/'.$docuname.'/'.$file,
                                  $home);
@@ -2266,9 +2367,13 @@ sub resizeImage {
 
 # --------------- Take an uploaded file and put it into the userfiles directory
 # input: $formname - the contents of the file are in $env{"form.$formname"}
-#                    the desired filenam is in $env{"form.$formname.filename"}
-#        $coursedoc - if true up to the current course
-#                     if false
+#                    the desired filename is in $env{"form.$formname.filename"}
+#        $context - possible values: coursedoc, existingfile, overwrite, 
+#                                    canceloverwrite, or ''. 
+#                   if 'coursedoc': upload to the current course
+#                   if 'existingfile': write file to tmp/overwrites directory 
+#                   if 'canceloverwrite': delete file written to tmp/overwrites directory
+#                   $context is passed as argument to &finishuserfileupload
 #        $subdir - directory in userfile to store the file into
 #        $parser - instruction to parse file for objects ($parser = parse)    
 #        $allfiles - reference to hash for embedded objects
@@ -2279,37 +2384,60 @@ sub resizeImage {
 #        $thumbheight - height (pixels) of thumbnail to make for uploaded image
 #        $resizewidth - width (pixels) to which to resize uploaded image
 #        $resizeheight - height (pixels) to which to resize uploaded image
+#        $mimetype - reference to scalar to accommodate mime type determined
+#                    from File::MMagic if $parser = parse.
 # 
 # output: url of file in userspace, or error: <message> 
 #             or /adm/notfound.html if failure to upload occurse
 
 sub userfileupload {
-    my ($formname,$coursedoc,$subdir,$parser,$allfiles,$codebase,$destuname,
-        $destudom,$thumbwidth,$thumbheight,$resizewidth,$resizeheight)=@_;
+    my ($formname,$context,$subdir,$parser,$allfiles,$codebase,$destuname,
+        $destudom,$thumbwidth,$thumbheight,$resizewidth,$resizeheight,$mimetype)=@_;
     if (!defined($subdir)) { $subdir='unknown'; }
     my $fname=$env{'form.'.$formname.'.filename'};
     $fname=&clean_filename($fname);
-# See if there is anything left
+    # See if there is anything left
     unless ($fname) { return 'error: no uploaded file'; }
-    chop($env{'form.'.$formname});
-    if (($formname eq 'screenshot') && ($subdir eq 'helprequests')) { #files uploaded to help request form are handled differently
+    # Files uploaded to help request form, or uploaded to "create course" page are handled differently
+    if ((($formname eq 'screenshot') && ($subdir eq 'helprequests')) ||
+        (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) ||
+         ($context eq 'existingfile') || ($context eq 'canceloverwrite')) {
         my $now = time;
-        my $filepath = 'tmp/helprequests/'.$now;
-        my @parts=split(/\//,$filepath);
-        my $fullpath = $perlvar{'lonDaemons'};
-        for (my $i=0;$i<@parts;$i++) {
-            $fullpath .= '/'.$parts[$i];
-            if ((-e $fullpath)!=1) {
-                mkdir($fullpath,0777);
+        my $filepath;
+        if (($formname eq 'screenshot') && ($subdir eq 'helprequests')) {
+             $filepath = 'tmp/helprequests/'.$now;
+        } elsif (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) {
+             $filepath = 'tmp/addcourse/'.$destudom.'/web/'.$env{'user.name'}.
+                         '_'.$env{'user.domain'}.'/pending';
+        } elsif (($context eq 'existingfile') || ($context eq 'canceloverwrite')) {
+            my ($docuname,$docudom);
+            if ($destudom) {
+                $docudom = $destudom;
+            } else {
+                $docudom = $env{'user.domain'};
+            }
+            if ($destuname) {
+                $docuname = $destuname;
+            } else {
+                $docuname = $env{'user.name'};
+            }
+            if (exists($env{'form.group'})) {
+                $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
+                $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
+            }
+            $filepath = 'tmp/overwrites/'.$docudom.'/'.$docuname.'/'.$subdir;
+            if ($context eq 'canceloverwrite') {
+                my $tempfile =  $perlvar{'lonDaemons'}.'/'.$filepath.'/'.$fname;
+                if (-e  $tempfile) {
+                    my @info = stat($tempfile);
+                    if ($info[9] eq $env{'form.timestamp'}) {
+                        unlink($tempfile);
+                    }
+                }
+                return;
             }
         }
-        open(my $fh,'>'.$fullpath.'/'.$fname);
-        print $fh $env{'form.'.$formname};
-        close($fh);
-        return $fullpath.'/'.$fname;
-    } elsif (($formname eq 'coursecreatorxml') && ($subdir eq 'batchupload')) { #files uploaded to create course page are handled differently
-        my $filepath = 'tmp/addcourse/'.$destudom.'/web/'.$env{'user.name'}.
-                       '_'.$env{'user.domain'}.'/pending';
+        # Create the directory if not present
         my @parts=split(/\//,$filepath);
         my $fullpath = $perlvar{'lonDaemons'};
         for (my $i=0;$i<@parts;$i++) {
@@ -2321,27 +2449,31 @@ sub userfileupload {
         open(my $fh,'>'.$fullpath.'/'.$fname);
         print $fh $env{'form.'.$formname};
         close($fh);
-        return $fullpath.'/'.$fname;
+        if ($context eq 'existingfile') {
+            my @info = stat($fullpath.'/'.$fname);
+            return ($fullpath.'/'.$fname,$info[9]);
+        } else {
+            return $fullpath.'/'.$fname;
+        }
     }
     if ($subdir eq 'scantron') {
         $fname = 'scantron_orig_'.$fname;
-    } else {   
-# Create the directory if not present
+    } else {
         $fname="$subdir/$fname";
     }
-    if ($coursedoc) {
+    if ($context eq 'coursedoc') {
 	my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
 	my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
         if ($env{'form.folder'} =~ m/^(default|supplemental)/) {
             return &finishuserfileupload($docuname,$docudom,
 					 $formname,$fname,$parser,$allfiles,
 					 $codebase,$thumbwidth,$thumbheight,
-                                         $resizewidth,$resizeheight);
+                                         $resizewidth,$resizeheight,$context,$mimetype);
         } else {
             $fname=$env{'form.folder'}.'/'.$fname;
             return &process_coursefile('uploaddoc',$docuname,$docudom,
 				       $fname,$formname,$parser,
-				       $allfiles,$codebase);
+				       $allfiles,$codebase,$mimetype);
         }
     } elsif (defined($destuname)) {
         my $docuname=$destuname;
@@ -2349,8 +2481,7 @@ sub userfileupload {
 	return &finishuserfileupload($docuname,$docudom,$formname,$fname,
 				     $parser,$allfiles,$codebase,
                                      $thumbwidth,$thumbheight,
-                                     $resizewidth,$resizeheight);
-        
+                                     $resizewidth,$resizeheight,$context,$mimetype);
     } else {
         my $docuname=$env{'user.name'};
         my $docudom=$env{'user.domain'};
@@ -2361,13 +2492,13 @@ sub userfileupload {
 	return &finishuserfileupload($docuname,$docudom,$formname,$fname,
 				     $parser,$allfiles,$codebase,
                                      $thumbwidth,$thumbheight,
-                                     $resizewidth,$resizeheight);
+                                     $resizewidth,$resizeheight,$context,$mimetype);
     }
 }
 
 sub finishuserfileupload {
     my ($docuname,$docudom,$formname,$fname,$parser,$allfiles,$codebase,
-        $thumbwidth,$thumbheight,$resizewidth,$resizeheight) = @_;
+        $thumbwidth,$thumbheight,$resizewidth,$resizeheight,$context,$mimetype) = @_;
     my $path=$docudom.'/'.$docuname.'/';
     my $filepath=$perlvar{'lonDocRoot'};
   
@@ -2393,7 +2524,23 @@ sub finishuserfileupload {
 	    print STDERR ('Failed to create '.$filepath.'/'.$file."\n");
 	    return '/adm/notfound.html';
 	}
-	if (!print FH ($env{'form.'.$formname})) {
+        if ($context eq 'overwrite') {
+            my $source =  $perlvar{'lonDaemons'}.'/tmp/overwrites/'.$docudom.'/'.$docuname.'/'.$fname;
+            my $target = $filepath.'/'.$file;
+            if (-e $source) {
+                my @info = stat($source);
+                if ($info[9] eq $env{'form.timestamp'}) {   
+                    unless (&File::Copy::move($source,$target)) {
+                        &logthis('Failed to overwrite '.$filepath.'/'.$file);
+                        return "Moving from $source failed";
+                    }
+                } else {
+                    return "Temporary file: $source had unexpected date/time for last modification";
+                }
+            } else {
+                return "Temporary file: $source missing";
+            }
+        } elsif (!print FH ($env{'form.'.$formname})) {
 	    &logthis('Failed to write to '.$filepath.'/'.$file);
 	    print STDERR ('Failed to write to '.$filepath.'/'.$file."\n");
 	    return '/adm/notfound.html';
@@ -2409,8 +2556,8 @@ sub finishuserfileupload {
     }
     if ($parser eq 'parse') {
         my $mm = new File::MMagic;
-        my $mime_type = $mm->checktype_filename($filepath.'/'.$file);
-        if ($mime_type eq 'text/html') {
+        my $type = $mm->checktype_filename($filepath.'/'.$file);
+        if ($type eq 'text/html') {
             my $parse_result = &extract_embedded_items($filepath.'/'.$file,
                                                        $allfiles,$codebase);
             unless ($parse_result eq 'ok') {
@@ -2418,6 +2565,9 @@ sub finishuserfileupload {
 	   	         ' for embedded media: '.$parse_result); 
             }
         }
+        if (ref($mimetype)) {
+            $$mimetype = $type;
+        }
     }
     if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) {
         my $input = $filepath.'/'.$file;
@@ -2951,8 +3101,9 @@ sub get_my_roles {
     unless (defined($uname)) { $uname=$env{'user.name'}; }
     unless (defined($udom)) { $udom=$env{'user.domain'}; }
     my (%dumphash,%nothide);
-    if ($context eq 'userroles') { 
-        %dumphash = &dump('roles',$udom,$uname);
+    if ($context eq 'userroles') {
+        my $extra = &freeze_escape({'skipcheck' => 1});
+        %dumphash = &dump('roles',$udom,$uname,'.',undef,$extra);
     } else {
         %dumphash=
             &dump('nohist_userroles',$udom,$uname);
@@ -3012,6 +3163,10 @@ sub get_my_roles {
                     if (!grep(/^cr$/,@{$roles})) {
                         next;
                     }
+                } elsif ($role =~ /^gr\//) {
+                    if (!grep(/^gr$/,@{$roles})) {
+                        next;
+                    }
                 } else {
                     next;
                 }
@@ -3854,6 +4009,44 @@ sub coursedescription {
     return %returnhash;
 }
 
+sub update_released_required {
+    my ($needsrelease,$cdom,$cnum,$chome,$cid) = @_;
+    if ($cdom eq '' || $cnum eq '' || $chome eq '' || $cid eq '') {
+        $cid = $env{'request.course.id'};
+        $cdom = $env{'course.'.$cid.'.domain'};
+        $cnum = $env{'course.'.$cid.'.num'};
+        $chome = $env{'course.'.$cid.'.home'};
+    }
+    if ($needsrelease) {
+        my %curr_reqd_hash = &userenvironment($cdom,$cnum,'internal.releaserequired');
+        my $needsupdate;
+        if ($curr_reqd_hash{'internal.releaserequired'} eq '') {
+            $needsupdate = 1;
+        } else {
+            my ($currmajor,$currminor) = split(/\./,$curr_reqd_hash{'internal.releaserequired'});
+            my ($needsmajor,$needsminor) = split(/\./,$needsrelease);
+            if (($currmajor < $needsmajor) || ($currmajor == $needsmajor && $currminor < $needsminor)) {
+                $needsupdate = 1;
+            }
+        }
+        if ($needsupdate) {
+            my %needshash = (
+                             'internal.releaserequired' => $needsrelease,
+                            );
+            my $putresult = &put('environment',\%needshash,$cdom,$cnum);
+            if ($putresult eq 'ok') {
+                &appenv({'course.'.$cid.'.internal.releaserequired' => $needsrelease});
+                my %crsinfo = &courseiddump($cdom,'.',1,'.','.',$cnum,undef,undef,'.');
+                if (ref($crsinfo{$cid}) eq 'HASH') {
+                    $crsinfo{$cid}{'releaserequired'} = $needsrelease;
+                    &courseidput($cdom,\%crsinfo,$chome,'notime');
+                }
+            }
+        }
+    }
+    return;
+}
+
 # -------------------------------------------------See if a user is privileged
 
 sub privileged {
@@ -3893,14 +4086,14 @@ sub rolesinit {
     my ($domain,$username,$authhost)=@_;
     my $now=time;
     my %userroles = ('user.login.time' => $now);
-    my $rolesdump=reply("dump:$domain:$username:roles",$authhost);
+    my $extra = &freeze_escape({'skipcheck' => 1});
+    my $rolesdump=reply("dump:$domain:$username:roles:.::$extra",$authhost);
     if (($rolesdump eq 'con_lost') || ($rolesdump eq '') || 
-        ($rolesdump =~ /^error:/)) { 
+        ($rolesdump =~ /^error:/)) {
         return \%userroles;
     }
     my %allroles=();
     my %allgroups=();   
-    my $group_privs;
 
     if ($rolesdump ne '') {
         foreach my $entry (split(/&/,$rolesdump)) {
@@ -3917,6 +4110,7 @@ sub rolesinit {
 		}
             } elsif ($role =~ m|^gr/|) {
                 ($trole,$tend,$tstart) = split(/_/,$role);
+                next if ($tstart eq '-1');
                 ($trole,$group_privs) = split(/\//,$trole);
                 $group_privs = &unescape($group_privs);
 	    } else {
@@ -4069,7 +4263,7 @@ sub set_userprivs {
             }
         }
         my $thesestr='';
-        foreach my $priv (keys(%thesepriv)) {
+        foreach my $priv (sort(keys(%thesepriv))) {
 	    $thesestr.=':'.$priv.'&'.$thesepriv{$priv};
 	}
         $userroles->{'user.priv.'.$role} = $thesestr;
@@ -4078,7 +4272,7 @@ sub set_userprivs {
 }
 
 sub role_status {
-    my ($rolekey,$then,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_;
+    my ($rolekey,$update,$refresh,$now,$role,$where,$trolecode,$tstatus,$tstart,$tend) = @_;
     my @pwhere = ();
     if (exists($env{$rolekey}) && $env{$rolekey} ne '') {
         (undef,undef,$$role,@pwhere)=split(/\./,$rolekey);
@@ -4087,7 +4281,7 @@ sub role_status {
             $$trolecode=$$role.'.'.$$where;
             ($$tstart,$$tend)=split(/\./,$env{$rolekey});
             $$tstatus='is';
-            if ($$tstart && $$tstart>$then) {
+            if ($$tstart && $$tstart>$update) {
                 $$tstatus='future';
                 if ($$tstart<$now) {
                     if ($$tstart && $$tstart>$refresh) {
@@ -4112,32 +4306,9 @@ sub role_status {
                                 $group_privs = &unescape($group_privs);
                                 &group_roleprivs(\%allgroups,$$where,$group_privs,$$tend,$$tstart);
                                 my %course_roles = &get_my_roles($env{'user.name'},$env{'user.domain'},'userroles',['active'],['cc','co','in','ta','ep','ad','st','cr'],[$tdomain],1);
-                                if (keys(%course_roles) > 0) {
-                                    my ($tnum) = ($trest =~ /^($match_courseid)/);
-                                    if ($tdomain ne '' && $tnum ne '') { 
-                                        foreach my $key (keys(%course_roles)) {
-                                            if ($key =~ /^\Q$tnum\E:\Q$tdomain\E:([^:]+):?([^:]*)/) {
-                                                my $crsrole = $1;
-                                                my $crssec = $2;
-                                                if ($crsrole =~ /^cr/) {
-                                                    unless (grep(/^cr$/,@rolecodes)) {
-                                                        push(@rolecodes,'cr');
-                                                    }
-                                                } else {
-                                                    unless(grep(/^\Q$crsrole\E$/,@rolecodes)) {
-                                                        push(@rolecodes,$crsrole);
-                                                    }
-                                                }
-                                                my $rolekey = $crsrole.'./'.$tdomain.'/'.$tnum;
-                                                if ($crssec ne '') {
-                                                    $rolekey .= '/'.$crssec;
-                                                }
-                                                $rolekey .= './';
-                                                $groups_roles{$rolekey} = \@rolecodes;
-                                            }
-                                        }
-                                    }
-                                }
+                                &get_groups_roles($tdomain,$trest,
+                                                  \%course_roles,\@rolecodes,
+                                                  \%groups_roles);
                             } else {
                                 push(@rolecodes,$$role);
                                 &standard_roleprivs(\%allroles,$$role,$tdomain,$spec,$trest,$$where);
@@ -4151,7 +4322,7 @@ sub role_status {
                 }
             }
             if ($$tend) {
-                if ($$tend<$then) {
+                if ($$tend<$update) {
                     $$tstatus='expired';
                 } elsif ($$tend<$now) {
                     $$tstatus='will_not';
@@ -4161,23 +4332,81 @@ sub role_status {
     }
 }
 
+sub get_groups_roles {
+    my ($cdom,$rest,$cdom_courseroles,$rolecodes,$groups_roles) = @_;
+    return unless((ref($cdom_courseroles) eq 'HASH') && 
+                  (ref($rolecodes) eq 'ARRAY') && 
+                  (ref($groups_roles) eq 'HASH')); 
+    if (keys(%{$cdom_courseroles}) > 0) {
+        my ($cnum) = ($rest =~ /^($match_courseid)/);
+        if ($cdom ne '' && $cnum ne '') {
+            foreach my $key (keys(%{$cdom_courseroles})) {
+                if ($key =~ /^\Q$cnum\E:\Q$cdom\E:([^:]+):?([^:]*)/) {
+                    my $crsrole = $1;
+                    my $crssec = $2;
+                    if ($crsrole =~ /^cr/) {
+                        unless (grep(/^cr$/,@{$rolecodes})) {
+                            push(@{$rolecodes},'cr');
+                        }
+                    } else {
+                        unless(grep(/^\Q$crsrole\E$/,@{$rolecodes})) {
+                            push(@{$rolecodes},$crsrole);
+                        }
+                    }
+                    my $rolekey = "$crsrole./$cdom/$cnum";
+                    if ($crssec ne '') {
+                        $rolekey .= "/$crssec";
+                    }
+                    $rolekey .= './';
+                    $groups_roles->{$rolekey} = $rolecodes;
+                }
+            }
+        }
+    }
+    return;
+}
+
+sub delete_env_groupprivs {
+    my ($where,$courseroles,$possroles) = @_;
+    return unless((ref($courseroles) eq 'HASH') && (ref($possroles) eq 'ARRAY'));
+    my ($dummy,$udom,$uname,$group) = split(/\//,$where);
+    unless (ref($courseroles->{$udom}) eq 'HASH') {
+        %{$courseroles->{$udom}} =
+            &get_my_roles('','','userroles',['active'],
+                          $possroles,[$udom],1);
+    }
+    if (ref($courseroles->{$udom}) eq 'HASH') {
+        foreach my $item (keys(%{$courseroles->{$udom}})) {
+            my ($cnum,$cdom,$crsrole,$crssec) = split(/:/,$item);
+            my $area = '/'.$cdom.'/'.$cnum;
+            my $privkey = "user.priv.$crsrole.$area";
+            if ($crssec ne '') {
+                $privkey .= '/'.$crssec;
+            }
+            $privkey .= ".$area/$group";
+            &Apache::lonnet::delenv($privkey,undef,[$crsrole]);
+        }
+    }
+    return;
+}
+
 sub check_adhoc_privs {
-    my ($cdom,$cnum,$then,$refresh,$now,$checkrole) = @_;
+    my ($cdom,$cnum,$update,$refresh,$now,$checkrole,$caller) = @_;
     my $cckey = 'user.role.'.$checkrole.'./'.$cdom.'/'.$cnum;
     if ($env{$cckey}) {
         my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend);
-        &role_status($cckey,$then,$refresh,$now,\$role,\$where,\$trolecode,\$tstatus,\$tstart,\$tend);
+        &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);
+            &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller);
         }
     } else {
-        &set_adhoc_privileges($cdom,$cnum,$checkrole);
+        &set_adhoc_privileges($cdom,$cnum,$checkrole,$caller);
     }
 }
 
 sub set_adhoc_privileges {
 # role can be cc or ca
-    my ($dcdom,$pickedcourse,$role) = @_;
+    my ($dcdom,$pickedcourse,$role,$caller) = @_;
     my $area = '/'.$dcdom.'/'.$pickedcourse;
     my $spec = $role.'.'.$area;
     my %userroles = &set_arearole($role,$area,'','',$env{'user.domain'},
@@ -4187,14 +4416,16 @@ sub set_adhoc_privileges {
     my ($author,$adv)= &set_userprivs(\%userroles,\%ccrole);
     &appenv(\%userroles,[$role,'cm']);
     &log($env{'user.domain'},$env{'user.name'},$env{'user.home'},"Role ".$role);
-    &appenv( {'request.role'        => $spec,
-              'request.role.domain' => $dcdom,
-              'request.course.sec'  => ''
-             }
-           );
-    my $tadv=0;
-    if (&allowed('adv') eq 'F') { $tadv=1; }
-    &appenv({'request.role.adv'    => $tadv});
+    unless ($caller eq 'constructaccess' && $env{'request.course.id'}) {
+        &appenv( {'request.role'        => $spec,
+                  'request.role.domain' => $dcdom,
+                  'request.course.sec'  => ''
+                 }
+               );
+        my $tadv=0;
+        if (&allowed('adv') eq 'F') { $tadv=1; }
+        &appenv({'request.role.adv'    => $tadv});
+    }
 }
 
 # --------------------------------------------------------------- get interface
@@ -4243,7 +4474,7 @@ sub del {
 # -------------------------------------------------------------- dump interface
 
 sub dump {
-    my ($namespace,$udomain,$uname,$regexp,$range)=@_;
+    my ($namespace,$udomain,$uname,$regexp,$range,$extra)=@_;
     if (!$udomain) { $udomain=$env{'user.domain'}; }
     if (!$uname) { $uname=$env{'user.name'}; }
     my $uhome=&homeserver($uname,$udomain);
@@ -4252,18 +4483,21 @@ sub dump {
     } else {
 	$regexp='.';
     }
-    my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range",$uhome);
+    my $rep=&reply("dump:$udomain:$uname:$namespace:$regexp:$range:$extra",$uhome);
     my @pairs=split(/\&/,$rep);
     my %returnhash=();
-    foreach my $item (@pairs) {
-	my ($key,$value)=split(/=/,$item,2);
-	$key = &unescape($key);
-	next if ($key =~ /^error: 2 /);
-	$returnhash{$key}=&thaw_unescape($value);
+    if (!($rep =~ /^error/ )) {
+	foreach my $item (@pairs) {
+	    my ($key,$value)=split(/=/,$item,2);
+	    $key = &unescape($key);
+	    next if ($key =~ /^error: 2 /);
+	    $returnhash{$key}=&thaw_unescape($value);
+	}
     }
     return %returnhash;
 }
 
+
 # --------------------------------------------------------- dumpstore interface
 
 sub dumpstore {
@@ -4797,7 +5031,7 @@ sub is_portfolio_file {
 }
 
 sub usertools_access {
-    my ($uname,$udom,$tool,$action,$context) = @_;
+    my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref)=@_;
     my ($access,%tools);
     if ($context eq '') {
         $context = 'tools';
@@ -4839,9 +5073,14 @@ sub usertools_access {
         $toolstatus = $env{'environment.'.$context.'.'.$tool};
         $inststatus = $env{'environment.inststatus'};
     } else {
-        my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus');
-        $toolstatus = $userenv{$context.'.'.$tool};
-        $inststatus = $userenv{'inststatus'};
+        if (ref($userenvref) eq 'HASH') {
+            $toolstatus = $userenvref->{$context.'.'.$tool};
+            $inststatus = $userenvref->{'inststatus'};
+        } else {
+            my %userenv = &userenvironment($udom,$uname,$context.'.'.$tool,'inststatus');
+            $toolstatus = $userenv{$context.'.'.$tool};
+            $inststatus = $userenv{'inststatus'};
+        }
     }
 
     if ($toolstatus ne '') {
@@ -4853,8 +5092,17 @@ sub usertools_access {
         return $access;
     }
 
-    my $is_adv = &is_advanced_user($udom,$uname);
-    my %domdef = &get_domain_defaults($udom);
+    my ($is_adv,%domdef);
+    if (ref($is_advref) eq 'HASH') {
+        $is_adv = $is_advref->{'is_adv'};
+    } else {
+        $is_adv = &is_advanced_user($udom,$uname);
+    }
+    if (ref($domdefref) eq 'HASH') {
+        %domdef = %{$domdefref};
+    } else {
+        %domdef = &get_domain_defaults($udom);
+    }
     if (ref($domdef{$tool}) eq 'HASH') {
         if ($is_adv) {
             if ($domdef{$tool}{'_LC_adv'} ne '') {
@@ -4928,6 +5176,11 @@ sub is_course_owner {
 
 sub is_advanced_user {
     my ($udom,$uname) = @_;
+    if ($udom ne '' && $uname ne '') {
+        if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
+            return $env{'user.adv'};  
+        }
+    }
     my %roleshash = &get_my_roles($uname,$udom,'userroles',undef,undef,undef,1);
     my %allroles;
     my $is_adv;
@@ -5443,7 +5696,7 @@ sub allowed {
        my $unamedom=$env{'user.name'}.':'.$env{'user.domain'};
        if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.roles.denied'}
 	   =~/\Q$rolecode\E/) {
-	   if ($priv ne 'pch') { 
+	   if (($priv ne 'pch') && ($priv ne 'plc')) { 
 	       &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'.
 			'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode.' in '.
 			$env{'request.course.id'});
@@ -5453,7 +5706,7 @@ sub allowed {
 
        if ($env{'course.'.$env{'request.course.id'}.'.'.$priv.'.users.denied'}
 	   =~/\Q$unamedom\E/) {
-	   if ($priv ne 'pch') { 
+	   if (($priv ne 'pch') && ($priv ne 'plc')) { 
 	       &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.
 			'Denied by user: '.$priv.' for '.$uri.' as '.$unamedom.' in '.
 			$env{'request.course.id'});
@@ -5467,7 +5720,7 @@ sub allowed {
    if ($thisallowed=~/R/) {
        my $rolecode=(split(/\./,$env{'request.role'}))[0];
        if (&metadata($uri,'roledeny')=~/\Q$rolecode\E/) {
-	   if ($priv ne 'pch') { 
+	   if (($priv ne 'pch') && ($priv ne 'plc')) { 
 	       &logthis($env{'user.domain'}.':'.$env{'user.name'}.':'.$env{'user.home'}.':'.
 			'Denied by role: '.$priv.' for '.$uri.' as '.$rolecode);
 	   }
@@ -5650,8 +5903,7 @@ sub update_allusers_table {
                'generation='.&escape($names->{'generation'}).'%%'.
                'permanentemail='.&escape($names->{'permanentemail'}).'%%'.
                'id='.&escape($names->{'id'}),$homeserver);
-    my $reply = &get_query_reply($queryid);
-    return $reply;
+    return;
 }
 
 # ------- Request retrieval of institutional classlists for course(s)
@@ -5821,9 +6073,9 @@ sub auto_get_sections {
 }
 
 sub auto_new_course {
-    my ($cnum,$cdom,$inst_course_id,$owner) = @_;
+    my ($cnum,$cdom,$inst_course_id,$owner,$coowners) = @_;
     my $homeserver = &homeserver($cnum,$cdom);
-    my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.$owner.':'.$cdom,$homeserver));
+    my $response=&unescape(&reply('autonewcourse:'.$inst_course_id.':'.&escape($owner).':'.$cdom.':'.&escape($coowners),$homeserver));
     return $response;
 }
 
@@ -6214,7 +6466,8 @@ sub get_users_groups {
     } else {  
         $grouplist = '';
         my $courseurl = &courseid_to_courseurl($courseid);
-        my %roleshash = &dump('roles',$udom,$uname,$courseurl);
+        my $extra = &freeze_escape({'skipcheck' => 1});
+        my %roleshash = &dump('roles',$udom,$uname,$courseurl,undef,$extra);
         my $access_end = $env{'course.'.$courseid.
                               '.default_enrollment_end_date'};
         my $now = time;
@@ -6574,6 +6827,10 @@ sub modifyuser {
              ' by '.$env{'user.name'}.' at '.$env{'user.domain'}.
              ' in domain '.$env{'request.role.domain'});
     my $uhome=&homeserver($uname,$udom,'true');
+    my $newuser;
+    if ($uhome eq 'no_host') {
+        $newuser = 1;
+    }
 # ----------------------------------------------------------------- Create User
     if (($uhome eq 'no_host') && 
 	(($umode && $upass) || ($umode eq 'localauth'))) {
@@ -6626,11 +6883,12 @@ sub modifyuser {
 		   ['firstname','middlename','lastname','generation','id',
                     'permanentemail','inststatus'],
 		   $udom,$uname);
-    my %names;
+    my (%names,%oldnames);
     if ($tmp[0] =~ m/^error:.*/) { 
         %names=(); 
     } else {
         %names = @tmp;
+        %oldnames = %names;
     }
 #
 # If name, email and/or uid are blank (e.g., because an uploaded file
@@ -6684,18 +6942,40 @@ sub modifyuser {
             }
         }
     }
-    my $reply = &put('environment', \%names, $udom,$uname);
-    if ($reply ne 'ok') { return 'error: '.$reply; }
-    my $sqlresult = &update_allusers_table($uname,$udom,\%names);
-    &devalidate_cache_new('namescache',$uname.':'.$udom);
-    my $logmsg = 'Success modifying user '.$udom.', '.$uname.', '.$uid.', '.
+    my $logmsg = $udom.', '.$uname.', '.$uid.', '.
                  $umode.', '.$first.', '.$middle.', '.
-	         $last.', '.$gene.', '.$email.', '.$inststatus;
+                 $last.', '.$gene.', '.$email.', '.$inststatus;
     if ($env{'user.name'} ne '' && $env{'user.domain'}) {
         $logmsg .= ' by '.$env{'user.name'}.' at '.$env{'user.domain'};
     } else {
         $logmsg .= ' during self creation';
     }
+    my $changed;
+    if ($newuser) {
+        $changed = 1;
+    } else {
+        foreach my $field (@fields) {
+            if ($names{$field} ne $oldnames{$field}) {
+                $changed = 1;
+                last;
+            }
+        }
+    }
+    unless ($changed) {
+        $logmsg = 'No changes in user information needed for: '.$logmsg;
+        &logthis($logmsg);
+        return 'ok';
+    }
+    my $reply = &put('environment', \%names, $udom,$uname);
+    if ($reply ne 'ok') { 
+        return 'error: '.$reply;
+    }
+    if ($names{'permanentemail'} ne $oldnames{'permanentemail'}) {
+        &Apache::lonnet::devalidate_cache_new('emailscache',$uname.':'.$udom);
+    }
+    my $sqlresult = &update_allusers_table($uname,$udom,\%names);
+    &devalidate_cache_new('namescache',$uname.':'.$udom);
+    $logmsg = 'Success modifying user '.$logmsg;
     &logthis($logmsg);
     return 'ok';
 }
@@ -7066,10 +7346,10 @@ sub diskusage {
 }
 
 sub is_locked {
-    my ($file_name, $domain, $user) = @_;
+    my ($file_name, $domain, $user, $which) = @_;
     my @check;
     my $is_locked;
-    push @check, $file_name;
+    push (@check,$file_name);
     my %locked = &get('file_permissions',\@check,
 		      $env{'user.domain'},$env{'user.name'});
     my ($tmp)=keys(%locked);
@@ -7078,14 +7358,19 @@ sub is_locked {
     if (ref($locked{$file_name}) eq 'ARRAY') {
         $is_locked = 'false';
         foreach my $entry (@{$locked{$file_name}}) {
-           if (ref($entry) eq 'ARRAY') { 
+           if (ref($entry) eq 'ARRAY') {
                $is_locked = 'true';
-               last;
+               if (ref($which) eq 'ARRAY') {
+                   push(@{$which},$entry);
+               } else {
+                   last;
+               }
            }
        }
     } else {
         $is_locked = 'false';
     }
+    return $is_locked;
 }
 
 sub declutter_portfile {
@@ -8236,7 +8521,7 @@ sub metadata {
     if (($uri eq '') || 
 	(($uri =~ m|^/*adm/|) && 
 	     ($uri !~ m|^adm/includes|) && ($uri !~ m|/bulletinboard$|)) ||
-        ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) ) {
+        ($uri =~ m|/$|) || ($uri =~ m|/.meta$|) || ($uri =~ m{^/*uploaded/.+\.sequence$}) || ($uri =~ m{^/*uploaded/$match_domain/$match_courseid/docs/})) {
 	return undef;
     }
     if (($uri =~ /^~/ || $uri =~ m{home/$match_username/public_html/}) 
@@ -8681,8 +8966,9 @@ sub symbverify {
             $thisurl =~ s/\?.+$//;
         }
         my $ids=$bighash{'ids_'.&clutter($thisurl)};
-        unless ($ids) { 
-           $ids=$bighash{'ids_/'.$thisurl};
+        unless ($ids) {
+            my $idkey = 'ids_'.($thisurl =~ m{^/}? '' : '/').$thisurl;  
+            $ids=$bighash{$idkey};
         }
         if ($ids) {
 # ------------------------------------------------------------------- Has ID(s)
@@ -8695,7 +8981,8 @@ sub symbverify {
   &symbclean(&declutter($bighash{'map_id_'.$mapid}).'___'.$resid.'___'.$thisfn)
    eq $symb) { 
 		   if (($env{'request.role.adv'}) ||
-		       $bighash{'encrypted_'.$id} eq $env{'request.enc'}) {
+		       ($bighash{'encrypted_'.$id} eq $env{'request.enc'}) ||
+                       ($thisurl eq '/adm/navmaps')) {
 		       $okay=1; 
 		   }
 	       }
@@ -9773,6 +10060,7 @@ sub get_dns {
     my %libserv;
     my $loaded;
     my %name_to_host;
+    my %internetdom;
 
     sub parse_hosts_tab {
 	my ($file) = @_;
@@ -9780,7 +10068,7 @@ sub get_dns {
 	    next if ($configline =~ /^(\#|\s*$ )/x);
 	    next if ($configline =~ /^\^/);
 	    chomp($configline);
-	    my ($id,$domain,$role,$name,$protocol)=split(/:/,$configline);
+	    my ($id,$domain,$role,$name,$protocol,$intdom)=split(/:/,$configline);
 	    $name=~s/\s//g;
 	    if ($id && $domain && $role && $name) {
 		$hostname{$id}=$name;
@@ -9796,6 +10084,9 @@ sub get_dns {
                 } else {
                     $protocol{$id} = 'http';
                 }
+                if (defined($intdom)) {
+                    $internetdom{$id} = $intdom;
+                }
 	    }
 	}
     }
@@ -9905,6 +10196,13 @@ sub get_dns {
 	my @uniq = grep(!$seen{$_}++, values(%hostdom));
 	return @uniq;
     }
+
+    sub internet_dom {
+        &load_hosts_tab() if (!$loaded);
+
+        my ($lonid) = @_;
+        return $internetdom{$lonid};
+    }
 }
 
 { 
@@ -10022,6 +10320,40 @@ sub get_dns {
         return undef;
     }
 
+    sub get_internet_names {
+        my ($lonid) = @_;
+        return if ($lonid eq '');
+        my ($idnref,$cached)=
+            &Apache::lonnet::is_cached_new('internetnames',$lonid);
+        if ($cached) {
+            return $idnref;
+        }
+        my $ip = &get_host_ip($lonid);
+        my @hosts = &get_hosts_from_ip($ip);
+        my %iphost = &get_iphost();
+        my (@idns,%seen);
+        foreach my $id (@hosts) {
+            my $dom = &host_domain($id);
+            my $prim_id = &domain($dom,'primary');
+            my $prim_ip = &get_host_ip($prim_id);
+            next if ($seen{$prim_ip});
+            if (ref($iphost{$prim_ip}) eq 'ARRAY') {
+                foreach my $id (@{$iphost{$prim_ip}}) {
+                    my $intdom = &internet_dom($id);
+                    unless (grep(/^\Q$intdom\E$/,@idns)) {
+                        push(@idns,$intdom);
+                    }
+                }
+            }
+            $seen{$prim_ip} = 1;
+        }
+        return &Apache::lonnet::do_cache_new('internetnames',$lonid,\@idns,12*60*60);
+    }
+
+}
+
+sub all_loncaparevs {
+    return qw(1.1 1.2 1.3 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10);
 }
 
 BEGIN {
@@ -10113,8 +10445,37 @@ BEGIN {
     }
 }
 
-sub all_loncaparevs {
-    return qw(1.1 1.2 1.3 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10);
+# ---------------------------------------------------------- Read serverhostID table
+{
+    if (-e "$perlvar{'lonTabDir'}/serverhomeIDs.tab") {
+        if (open(my $config,"<$perlvar{'lonTabDir'}/serverhomeIDs.tab")) {
+            while (my $configline=<$config>) {
+                chomp($configline);
+                my ($name,$id)=split(/:/,$configline);
+                $serverhomeIDs{$name}=$id;
+            }
+            close($config);
+        }
+    }
+}
+
+{
+    my $file = $Apache::lonnet::perlvar{'lonTabDir'}.'/releaseslist.xml';
+    if (-e $file) {
+        my $parser = HTML::LCParser->new($file);
+        while (my $token = $parser->get_token()) {
+            if ($token->[0] eq 'S') {
+                my $item = $token->[1];
+                my $name = $token->[2]{'name'};
+                my $value = $token->[2]{'value'};
+                if ($item ne '' && $name ne '' && $value ne '') {
+                    my $release = $parser->get_text();
+                    $release =~ s/(^\s*|\s*$ )//gx;
+                    $needsrelease{$item.':'.$name.':'.$value} = $release;
+                }
+            }
+        }
+    }
 }
 
 # ------------- set up temporary directory
@@ -11103,8 +11464,10 @@ userfileupload(): main rotine for puttin
            filename, and the contents of the file to create/modifed exist
            the filename is in $env{'form.'.$formname.'.filename'} and the
            contents of the file is located in $env{'form.'.$formname}
- coursedoc - if true, store the file in the course of the active role
-             of the current user
+ context - if coursedoc, store the file in the course of the active role
+             of the current user; 
+           if 'existingfile': store in 'overwrites' in /home/httpd/perl/tmp
+           if 'canceloverwrite': delete file in tmp/overwrites directory
  subdir - required - subdirectory to put the file in under ../userfiles/
          if undefined, it will be placed in "unknown"
 
@@ -11126,16 +11489,29 @@ returns: the new clean filename
 
 =item *
 
-finishuserfileupload(): routine that creaes and sends the file to
+finishuserfileupload(): routine that creates and sends the file to
 userspace, probably shouldn't be called directly
 
   docuname: username or courseid of destination for the file
   docudom: domain of user/course of destination for the file
   formname: same as for userfileupload()
-  fname: filename (inculding subdirectories) for the file
+  fname: filename (including subdirectories) for the file
+  parser: if 'parse', will parse (html) file to extract references to objects, links etc.
+  allfiles: reference to hash used to store objects found by parser
+  codebase: reference to hash used for codebases of java objects found by parser
+  thumbwidth: width (pixels) of thumbnail to be created for uploaded image
+  thumbheight: height (pixels) of thumbnail to be created for uploaded image
+  resizewidth: width to be used to resize image using resizeImage from ImageMagick
+  resizeheight: height to be used to resize image using resizeImage from ImageMagick
+  context: if 'overwrite', will move the uploaded file from its temporary location to
+            userfiles to facilitate overwriting a previously uploaded file with same name.
+  mimetype: reference to scalar to accommodate mime type determined
+            from File::MMagic if $parser = parse.
 
  returns either the url of the uploaded file (/uploaded/....) if successful
- and /adm/notfound.html if unsuccessful
+ and /adm/notfound.html if unsuccessful (or an error message if context 
+ was 'overwrite').
+ 
 
 =item *