--- loncom/lonnet/perl/lonnet.pm	2008/02/21 10:04:35	1.942
+++ loncom/lonnet/perl/lonnet.pm	2009/01/05 16:29:24	1.976.2.6
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.942 2008/02/21 10:04:35 foxr Exp $
+# $Id: lonnet.pm,v 1.976.2.6 2009/01/05 16:29:24 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -27,6 +27,47 @@
 #
 ###
 
+=pod
+
+=head1 NAME
+
+Apache::lonnet.pm
+
+=head1 SYNOPSIS
+
+This file is an interface to the lonc processes of
+the LON-CAPA network as well as set of elaborated functions for handling information
+necessary for navigating through a given cluster of LON-CAPA machines within a
+domain. There are over 40 specialized functions in this module which handle the
+reading and transmission of metadata, user information (ids, names, environments, roles,
+logs), file information (storage, reading, directories, extensions, replication, embedded
+styles and descriptors), educational resources (course descriptions, section names and
+numbers), url hashing (to assign roles on a url basis), and translating abbreviated symbols to
+and from more descriptive phrases or explanations.
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+=head1 Package Variables
+
+These are largely undocumented, so if you decipher one please note it here.
+
+=over 4
+
+=item $processmarker
+
+Contains the time this process was started and this servers host id.
+
+=item $dumpcount
+
+Counts the number of times a message log flush has been attempted (regardless
+of success) by this process.  Used as part of the filename when messages are
+delayed.
+
+=back
+
+=cut
+
 package Apache::lonnet;
 
 use strict;
@@ -34,12 +75,12 @@ use LWP::UserAgent();
 use HTTP::Date;
 # use Date::Parse;
 use vars qw(%perlvar %spareid %pr %prp $memcache %packagetab $tmpdir
-            $_64bit %env);
+            $_64bit %env %protocol);
 
 my (%badServerCache, $memcache, %courselogs, %accesshash, %domainrolehash,
     %userrolehash, $processmarker, $dumpcount, %coursedombuf,
     %coursenumbuf, %coursehombuf, %coursedescrbuf, %courseinstcodebuf,
-    %courseownerbuf, %coursetypebuf);
+    %courseownerbuf, %coursetypebuf,$locknum);
 
 use IO::Socket;
 use GDBM_File;
@@ -61,51 +102,31 @@ require Exporter;
 our @ISA = qw (Exporter);
 our @EXPORT = qw(%env);
 
-=pod
-
-=head1 Package Variables
-
-These are largely undocumented, so if you decipher one please note it here.
-
-=over 4
-
-=item $processmarker
-
-Contains the time this process was started and this servers host id.
-
-=item $dumpcount
-
-Counts the number of times a message log flush has been attempted (regardless
-of success) by this process.  Used as part of the filename when messages are
-delayed.
-
-=back
-
-=cut
-
 
 # --------------------------------------------------------------------- Logging
 {
     my $logid;
     sub instructor_log {
-	my ($hash_name,$storehash,$delflag,$uname,$udom)=@_;
+	my ($hash_name,$storehash,$delflag,$uname,$udom,$cnum,$cdom)=@_;
+        if (($cnum eq '') || ($cdom eq '')) {
+            $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+            $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+        }
 	$logid++;
-	my $id=time().'00000'.$$.'00000'.$logid;
+        my $now = time();
+	my $id=$now.'00000'.$$.'00000'.$logid;
 	return &Apache::lonnet::put('nohist_'.$hash_name,
 				    { $id => {
 					'exe_uname' => $env{'user.name'},
 					'exe_udom'  => $env{'user.domain'},
-					'exe_time'  => time(),
+					'exe_time'  => $now,
 					'exe_ip'    => $ENV{'REMOTE_ADDR'},
 					'delflag'   => $delflag,
 					'logentry'  => $storehash,
 					'uname'     => $uname,
 					'udom'      => $udom,
 				    }
-				  },
-				    $env{'course.'.$env{'request.course.id'}.'.domain'},
-				    $env{'course.'.$env{'request.course.id'}.'.num'}
-				    );
+				  },$cdom,$cnum);
     }
 }
 
@@ -156,6 +177,20 @@ sub create_connection {
     return 0;
 }
 
+sub get_server_timezone {
+    my ($cnum,$cdom) = @_;
+    my $home=&homeserver($cnum,$cdom);
+    if ($home ne 'no_host') {
+        my $cachetime = 24*3600;
+        my ($timezone,$cached)=&is_cached_new('servertimezone',$home);
+        if (defined($cached)) {
+            return $timezone;
+        } else {
+            my $timezone = &reply('servertimezone',$home);
+            return &do_cache_new('servertimezone',$home,$timezone,$cachetime);
+        }
+    }
+}
 
 # -------------------------------------------------- Non-critical communication
 sub subreply {
@@ -448,27 +483,39 @@ sub timed_flock {
 # ---------------------------------------------------------- Append Environment
 
 sub appenv {
-    my %newenv=@_;
-    foreach my $key (keys(%newenv)) {
-	if (($newenv{$key}=~/^user\.role/) || ($newenv{$key}=~/^user\.priv/)) {
-            &logthis("<font color=\"blue\">WARNING: ".
-                "Attempt to modify environment ".$key." to ".$newenv{$key}
-                .'</font>');
-	    delete($newenv{$key});
-        } else {
-            $env{$key}=$newenv{$key};
+    my ($newenv,$roles) = @_;
+    if (ref($newenv) eq 'HASH') {
+        foreach my $key (keys(%{$newenv})) {
+            my $refused = 0;
+	    if (($key =~ /^user\.role/) || ($key =~ /^user\.priv/)) {
+                $refused = 1;
+                if (ref($roles) eq 'ARRAY') {
+                    my ($type,$role) = ($key =~ /^user\.(role|priv)\.([^.]+)\./);
+                    if (grep(/^\Q$role\E$/,@{$roles})) {
+                        $refused = 0;
+                    }
+                }
+            }
+            if ($refused) {
+                &logthis("<font color=\"blue\">WARNING: ".
+                         "Attempt to modify environment ".$key." to ".$newenv->{$key}
+                         .'</font>');
+	        delete($newenv->{$key});
+            } else {
+                $env{$key}=$newenv->{$key};
+            }
+        }
+        my $opened = open(my $env_file,'+<',$env{'user.environment'});
+        if ($opened
+	    && &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;
+	    }
+	    untie(%disk_env);
         }
-    }
-    my $opened = open(my $env_file,'+<',$env{'user.environment'});
-    if ($opened
-	&& &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;
-	}
-	untie(%disk_env);
     }
     return 'ok';
 }
@@ -488,7 +535,7 @@ sub delenv {
 	tie(my %disk_env,'GDBM_File',$env{'user.environment'},
 	    (&GDBM_WRITER()|&GDBM_NOLOCK()),0640)) {
 	foreach my $key (keys(%disk_env)) {
-	    if ($key=~/^$delthis/) { 
+	    if ($key=~/^\Q$delthis\E/) { 
 		delete($env{$key});
 		delete($disk_env{$key});
 	    }
@@ -512,6 +559,51 @@ sub get_env_multiple {
     return(@values);
 }
 
+# ------------------------------------------------------------------- Locking
+
+sub set_lock {
+    my ($text)=@_;
+    $locknum++;
+    my $id=$$.'-'.$locknum;
+    &appenv({'session.locks' => $env{'session.locks'}.','.$id,
+             'session.lock.'.$id => $text});
+    return $id;
+}
+
+sub get_locks {
+    my $num=0;
+    my %texts=();
+    foreach my $lock (split(/\,/,$env{'session.locks'})) {
+       if ($lock=~/\w/) {
+          $num++;
+          $texts{$lock}=$env{'session.lock.'.$lock};
+       }
+   }
+   return ($num,%texts);
+}
+
+sub remove_lock {
+    my ($id)=@_;
+    my $newlocks='';
+    foreach my $lock (split(/\,/,$env{'session.locks'})) {
+       if (($lock=~/\w/) && ($lock ne $id)) {
+          $newlocks.=','.$lock;
+       }
+    }
+    &appenv({'session.locks' => $newlocks});
+    &delenv('session.lock.'.$id);
+}
+
+sub remove_all_locks {
+    my $activelocks=$env{'session.locks'};
+    foreach my $lock (split(/\,/,$env{'session.locks'})) {
+       if ($lock=~/\w/) {
+          &remove_lock($lock);
+       }
+    }
+}
+
+
 # ------------------------------------------ Find out current server userload
 sub userload {
     my $numusers=0;
@@ -584,7 +676,11 @@ sub spareserver {
     }
 
     if (!$want_server_name) {
-	$spare_server="http://".&hostname($spare_server);
+        my $protocol = 'http';
+        if ($protocol{$spare_server} eq 'https') {
+            $protocol = $protocol{$spare_server};
+        }
+	$spare_server = $protocol.'://'.&hostname($spare_server);
     }
     return $spare_server;
 }
@@ -693,24 +789,38 @@ sub queryauthenticate {
 # --------- Try to authenticate user from domain's lib servers (first this one)
 
 sub authenticate {
-    my ($uname,$upass,$udom)=@_;
+    my ($uname,$upass,$udom,$checkdefauth)=@_;
     $upass=&escape($upass);
     $uname= &LONCAPA::clean_username($uname);
     my $uhome=&homeserver($uname,$udom,1);
+    my $newhome;
     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);
+	$uhome=&homeserver($uname,$udom,1);
+        if (($uhome eq 'no_host') && $checkdefauth) {
+            if (defined(&domain($udom,'primary'))) {
+                $newhome=&domain($udom,'primary');
+            }
+            if ($newhome ne '') {
+                $uhome = $newhome;
+            }
+        }
 	if ((!$uhome) || ($uhome eq 'no_host')) {
 	    &logthis("User $uname at $udom is unknown in authenticate");
-	}
-	return 'no_host';
+	    return 'no_host';
+        }
     }
-    my $answer=reply("encrypt:auth:$udom:$uname:$upass",$uhome);
+    my $answer=reply("encrypt:auth:$udom:$uname:$upass:$checkdefauth",$uhome);
     if ($answer eq 'authorized') {
-	&logthis("User $uname at $udom authorized by $uhome"); 
-	return $uhome; 
+        if ($newhome) {
+            &logthis("User $uname at $udom authorized by $uhome, but needs account");
+            return 'no_account_on_host'; 
+        } else {
+            &logthis("User $uname at $udom authorized by $uhome");
+            return $uhome;
+        }
     }
     if ($answer eq 'non_authorized') {
 	&logthis("User $uname at $udom rejected by $uhome");
@@ -885,6 +995,10 @@ sub retrieve_inst_usertypes {
     if (defined(&domain($udom,'primary'))) {
         my $uhome=&domain($udom,'primary');
         my $rep=&reply("inst_usertypes:$udom",$uhome);
+        if ($rep =~ /^(con_lost|error|no_such_host|refused)/) {
+            &logthis("get_dom failed - $rep returned from $uhome in domain: $udom");
+            return (\%returnhash,\@order);
+        }
         my ($hashitems,$orderitems) = split(/:/,$rep); 
         my @pairs=split(/\&/,$hashitems);
         foreach my $item (@pairs) {
@@ -1064,6 +1178,10 @@ sub inst_rulecheck {
                     $response=&unescape(&reply('instidrulecheck:'.&escape($udom).
                                               ':'.&escape($id).':'.$rulestr,
                                               $homeserver));
+                } elsif ($item eq 'selfcreate') {
+                    $response=&unescape(&reply('instselfcreatecheck:'.
+                                               &escape($udom).':'.&escape($uname).
+                                              ':'.$rulestr,$homeserver));
                 }
                 if ($response ne 'refused') {
                     my @pairs=split(/\&/,$response);
@@ -1090,6 +1208,9 @@ sub inst_userrules {
             if ($check eq 'id') {
                 $response=&reply('instidrules:'.&escape($udom),
                                  $homeserver);
+            } elsif ($check eq 'email') {
+                $response=&reply('instemailrules:'.&escape($udom),
+                                 $homeserver);
             } else {
                 $response=&reply('instuserrules:'.&escape($udom),
                                  $homeserver);
@@ -1115,6 +1236,49 @@ sub inst_userrules {
     return (\%ruleshash,\@ruleorder);
 }
 
+# ------------- Get Authentication, Language and User Tools Defaults for Domain
+
+sub get_domain_defaults {
+    my ($domain) = @_;
+    my $cachetime = 60*60*24;
+    my ($result,$cached)=&is_cached_new('domdefaults',$domain);
+    if (defined($cached)) {
+        if (ref($result) eq 'HASH') {
+            return %{$result};
+        }
+    }
+    my %domdefaults;
+    my %domconfig =
+         &Apache::lonnet::get_dom('configuration',['defaults','quotas'],$domain);
+    if (ref($domconfig{'defaults'}) eq 'HASH') {
+        $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
+        $domdefaults{'auth_def'} = $domconfig{'defaults'}{'auth_def'};
+        $domdefaults{'auth_arg_def'} = $domconfig{'defaults'}{'auth_arg_def'};
+        $domdefaults{'timezone_def'} = $domconfig{'defaults'}{'timezone_def'};
+        $domdefaults{'datelocale_def'} = $domconfig{'defaults'}{'datelocale_def'};
+    } else {
+        $domdefaults{'lang_def'} = &domain($domain,'lang_def');
+        $domdefaults{'auth_def'} = &domain($domain,'auth_def');
+        $domdefaults{'auth_arg_def'} = &domain($domain,'auth_arg_def');
+    }
+    if (ref($domconfig{'quotas'}) eq 'HASH') {
+        if (ref($domconfig{'quotas'}{'defaultquota'}) eq 'HASH') {
+            $domdefaults{'defaultquota'} = $domconfig{'quotas'}{'defaultquota'};
+        } else {
+            $domdefaults{'defaultquota'} = $domconfig{'quotas'};
+        } 
+        my @usertools = ('aboutme','blog','portfolio');
+        foreach my $item (@usertools) {
+            if (ref($domconfig{'quotas'}{$item}) eq 'HASH') {
+                $domdefaults{$item} = $domconfig{'quotas'}{$item};
+            }
+        }
+    }
+    &Apache::lonnet::do_cache_new('domdefaults',$domain,\%domdefaults,
+                                  $cachetime);
+    return %domdefaults;
+}
+
 # --------------------------------------------------- Assign a key to a student
 
 sub assign_access_key {
@@ -1147,7 +1311,7 @@ sub assign_access_key {
 # key now belongs to user
 	    my $envkey='key.'.$cdom.'_'.$cnum;
             if (&put('environment',{$envkey => $ckey}) eq 'ok') {
-                &appenv('environment.'.$envkey => $ckey);
+                &appenv({'environment.'.$envkey => $ckey});
                 return 'ok';
             } else {
                 return 
@@ -1434,9 +1598,14 @@ sub purge_remembered {
 
 sub userenvironment {
     my ($udom,$unam,@what)=@_;
+    my $items;
+    foreach my $item (@what) {
+        $items.=&escape($item).'&';
+    }
+    $items=~s/\&$//;
     my %returnhash=();
     my @answer=split(/\&/,
-                &reply('get:'.$udom.':'.$unam.':environment:'.join('&',@what),
+                &reply('get:'.$udom.':'.$unam.':environment:'.$items,
                       &homeserver($unam,$udom)));
     my $i;
     for ($i=0;$i<=$#what;$i++) {
@@ -1631,12 +1800,21 @@ sub ssi_body {
     if (! exists($form{'LONCAPA_INTERNAL_no_discussion'})) {
         $form{'LONCAPA_INTERNAL_no_discussion'}='true';
     }
-    my $output=($filelink=~/^http\:/?&externalssi($filelink):
-                                     &ssi($filelink,%form));
+    my $output='';
+    my $response;
+    if ($filelink=~/^https?\:/) {
+       ($output,$response)=&externalssi($filelink);
+    } else {
+       ($output,$response)=&ssi($filelink,%form);
+    }
     $output=~s|//(\s*<!--)? BEGIN LON-CAPA Internal.+?// END LON-CAPA Internal\s*(-->)?\s||gs;
     $output=~s/^.*?\<body[^\>]*\>//si;
     $output=~s/\<\/body\s*\>.*?$//si;
-    return $output;
+    if (wantarray) {
+        return ($output, $response);
+    } else {
+        return $output;
+    }
 }
 
 # --------------------------------------------------------- Server Side Include
@@ -1656,18 +1834,14 @@ sub absolute_url {
 #  fn     Possibly encrypted resource name/id.
 #  form   Hash that describes how the rendering should be done
 #         and other things.
-#  r      Optional reference that will be given the response.
-#         This is mostly provided so that the caller can implement
-#         error detection, recovery and retry policies.
-#     
 # Returns:
-#    The content of the response.
+#   Scalar context: The content of the response.
+#   Array context:  2 element list of the content and the full response object.
+#     
 sub ssi {
 
-    my ($fn,%form, $r)=@_;
-
+    my ($fn,%form)=@_;
     my $ua=new LWP::UserAgent;
-    
     my $request;
 
     $form{'no_update_last_known'}=1;
@@ -1682,11 +1856,11 @@ sub ssi {
     $request->header(Cookie => $ENV{'HTTP_COOKIE'});
     my $response=$ua->request($request);
 
-    if ($r) {
-	$$r = $response;
+    if (wantarray) {
+	return ($response->content, $response);
+    } else {
+	return $response->content;
     }
-
-    return $response->content;
 }
 
 sub externalssi {
@@ -1694,7 +1868,11 @@ sub externalssi {
     my $ua=new LWP::UserAgent;
     my $request=new HTTP::Request('GET',$url);
     my $response=$ua->request($request);
-    return $response->content;
+    if (wantarray) {
+        return ($response->content, $response);
+    } else {
+        return $response->content;
+    }
 }
 
 # -------------------------------- Allow a /uploaded/ URI to be vouched for
@@ -1707,7 +1885,7 @@ sub allowuploaded {
     my %httpref=();
     my $httpurl=&hreflocation('',$url);
     $httpref{'httpref.'.$httpurl}=$srcurl;
-    &Apache::lonnet::appenv(%httpref);
+    &Apache::lonnet::appenv(\%httpref);
 }
 
 # --------- File operations in /home/httpd/html/userfiles/$domain/1/2/3/$course
@@ -1768,7 +1946,7 @@ sub process_coursefile {
             print $fh $env{'form.'.$source};
             close($fh);
             if ($parser eq 'parse') {
-                my $parse_result = &extract_embedded_items($filepath,$fname,$allfiles,$codebase);
+                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);
                 }
@@ -1976,7 +2154,7 @@ sub finishuserfileupload {
 	close(FH);
     }
     if ($parser eq 'parse') {
-        my $parse_result = &extract_embedded_items($filepath,$file,$allfiles,
+        my $parse_result = &extract_embedded_items($filepath.'/'.$file,$allfiles,
 						   $codebase);
         unless ($parse_result eq 'ok') {
             &logthis('Failed to parse '.$filepath.$file.
@@ -2016,7 +2194,7 @@ sub finishuserfileupload {
 }
 
 sub extract_embedded_items {
-    my ($filepath,$file,$allfiles,$codebase,$content) = @_;
+    my ($fullpath,$allfiles,$codebase,$content) = @_;
     my @state = ();
     my %javafiles = (
                       codebase => '',
@@ -2031,7 +2209,7 @@ sub extract_embedded_items {
     if ($content) {
         $p = HTML::LCParser->new($content);
     } else {
-        $p = HTML::LCParser->new($filepath.'/'.$file);
+        $p = HTML::LCParser->new($fullpath);
     }
     while (my $t=$p->get_token()) {
 	if ($t->[0] eq 'S') {
@@ -2344,7 +2522,12 @@ sub courseacclog {
         # FIXME: Probably ought to escape things....
 	foreach my $key (keys(%env)) {
             if ($key=~/^form\.(.*)/) {
-		$what.=':'.$1.'='.$env{$key};
+                my $formitem = $1;
+                if ($formitem =~ /^HWFILE(?:SIZE|TOOBIG)/) {
+                    $what.=':'.$formitem.'='.$env{$key};
+                } elsif ($formitem !~ /^HWFILE(?:[^.]+)$/) {
+                    $what.=':'.$formitem.'='.$env{$key};
+                }
             }
         }
     } elsif ($fnsymb =~ m:^/adm/searchcat:) {
@@ -2409,8 +2592,38 @@ sub userrolelog {
     }
 }
 
+sub courserolelog {
+    my ($trole,$username,$domain,$area,$tstart,$tend,$delflag,$selfenroll,$context)=@_;
+    if (($trole eq 'cc') || ($trole eq 'in') ||
+        ($trole eq 'ep') || ($trole eq 'ad') ||
+        ($trole eq 'ta') || ($trole eq 'st') ||
+        ($trole=~/^cr/) || ($trole eq 'gr')) {
+        if ($area =~ m-^/($match_domain)/($match_courseid)/?([^/]*)-) {
+            my $cdom = $1;
+            my $cnum = $2;
+            my $sec = $3;
+            my $namespace = 'rolelog';
+            my %storehash = (
+                               role    => $trole,
+                               start   => $tstart,
+                               end     => $tend,
+                               selfenroll => $selfenroll,
+                               context    => $context,
+                            );
+            if ($trole eq 'gr') {
+                $namespace = 'groupslog';
+                $storehash{'group'} = $sec;
+            } else {
+                $storehash{'section'} = $sec;
+            }
+            &instructor_log($namespace,\%storehash,$delflag,$username,$domain,$cnum,$cdom);
+        }
+    }
+    return;
+}
+
 sub get_course_adv_roles {
-    my $cid=shift;
+    my ($cid,$codes) = @_;
     $cid=$env{'request.course.id'} unless (defined($cid));
     my %coursehash=&coursedescription($cid);
     my %nothide=();
@@ -2435,14 +2648,23 @@ sub get_course_adv_roles {
 	if ((&privileged($username,$domain)) && 
 	    (!$nothide{$username.':'.$domain})) { next; }
 	if ($role eq 'cr') { next; }
-        my $key=&plaintext($role);
-        if ($section) { $key.=' (Sec/Grp '.$section.')'; }
-        if ($returnhash{$key}) {
-	    $returnhash{$key}.=','.$username.':'.$domain;
+        if ($codes) {
+            if ($section) { $role .= ':'.$section; }
+            if ($returnhash{$role}) {
+                $returnhash{$role}.=','.$username.':'.$domain;
+            } else {
+                $returnhash{$role}=$username.':'.$domain;
+            }
         } else {
-            $returnhash{$key}=$username.':'.$domain;
+            my $key=&plaintext($role);
+            if ($section) { $key.=' ('.&Apache::lonlocal::mt('Section [_1]',$section).')'; }
+            if ($returnhash{$key}) {
+	        $returnhash{$key}.=','.$username.':'.$domain;
+            } else {
+                $returnhash{$key}=$username.':'.$domain;
+            }
         }
-     }
+    }
     return %returnhash;
 }
 
@@ -2604,7 +2826,8 @@ sub courseidput {
 
 sub courseiddump {
     my ($domfilter,$descfilter,$sincefilter,$instcodefilter,$ownerfilter,
-        $coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok)=@_;
+        $coursefilter,$hostidflag,$hostidref,$typefilter,$regexp_ok,
+        $selfenrollonly,$catfilter,$showhidden,$caller)=@_;
     my $as_hash = 1;
     my %returnhash;
     if (!$domfilter) { $domfilter=''; }
@@ -2621,7 +2844,9 @@ sub courseiddump {
                          $sincefilter.':'.&escape($descfilter).':'.
                          &escape($instcodefilter).':'.&escape($ownerfilter).
                          ':'.&escape($coursefilter).':'.&escape($typefilter).
-                         ':'.&escape($regexp_ok).':'.$as_hash,$tryserver);
+                         ':'.&escape($regexp_ok).':'.$as_hash.':'.
+                         &escape($selfenrollonly).':'.&escape($catfilter).':'.
+                         $showhidden.':'.$caller,$tryserver);
                 my @pairs=split(/\&/,$rep);
                 foreach my $item (@pairs) {
                     my ($key,$value)=split(/\=/,$item,2);
@@ -3358,7 +3583,7 @@ sub coursedescription {
        }
     }
     if (!$args->{'one_time'}) {
-	&appenv(%envhash);
+	&appenv(\%envhash);
     }
     return %returnhash;
 }
@@ -3397,12 +3622,13 @@ sub privileged {
 
 sub rolesinit {
     my ($domain,$username,$authhost)=@_;
+    my %userroles;
     my $rolesdump=reply("dump:$domain:$username:roles",$authhost);
-    if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return ''; }
+    if (($rolesdump eq 'con_lost') || ($rolesdump eq '')) { return \%userroles; }
     my %allroles=();
     my %allgroups=();   
     my $now=time;
-    my %userroles = ('user.login.time' => $now);
+    %userroles = ('user.login.time' => $now);
     my $group_privs;
 
     if ($rolesdump ne '') {
@@ -3907,6 +4133,7 @@ sub tmpget {
     my %returnhash;
     foreach my $item (split(/\&/,$rep)) {
 	my ($key,$value)=split(/=/,$item);
+        next if ($key =~ /^error: 2 /);
 	$returnhash{&unescape($key)}=&thaw_unescape($value);
     }
     return %returnhash;
@@ -4162,6 +4389,129 @@ sub is_portfolio_file {
     return;
 }
 
+sub usertools_access {
+    my ($uname,$udom,$tool,$action) = @_;
+    my $access;
+    my %tools = (
+                  aboutme   => 1,
+                  blog      => 1,
+                  portfolio => 1,
+                );
+    return if (!defined($tools{$tool}));
+
+    if ((!defined($udom)) || (!defined($uname))) {
+        $udom = $env{'user.domain'};
+        $uname = $env{'user.name'};
+    }
+
+    if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
+        if ($action ne 'reload') {
+            return $env{'environment.availabletools.'.$tool};
+        }
+    }
+
+    my ($toolstatus,$inststatus);
+
+    if (($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
+        $toolstatus = $env{'environment.tools.'.$tool};
+        $inststatus = $env{'environment.inststatus'};
+    } else {
+        my %userenv = &userenvironment($udom,$uname,'tools.'.$tool);
+        $toolstatus = $userenv{'tools.'.$tool};
+        $inststatus = $userenv{'inststatus'};
+    }
+
+    if ($toolstatus ne '') {
+        if ($toolstatus) {
+            $access = 1;
+        } else {
+            $access = 0;
+        }
+        return $access;
+    }
+
+    my $is_adv = &is_advanced_user($udom,$uname);
+    my %domdef = &get_domain_defaults($udom);
+    if (ref($domdef{$tool}) eq 'HASH') {
+        if ($is_adv) {
+            if ($domdef{$tool}{'_LC_adv'} ne '') {
+                if ($domdef{$tool}{'_LC_adv'}) { 
+                    $access = 1;
+                } else {
+                    $access = 0;
+                }
+                return $access;
+            }
+        }
+        if ($inststatus ne '') {
+            my ($hasaccess,$hasnoaccess);
+            foreach my $affiliation (split(/:/,$inststatus)) {
+                if ($domdef{$tool}{$affiliation} ne '') { 
+                    if ($domdef{$tool}{$affiliation}) {
+                        $hasaccess = 1;
+                    } else {
+                        $hasnoaccess = 1;
+                    }
+                }
+            }
+            if ($hasaccess || $hasnoaccess) {
+                if ($hasaccess) {
+                    $access = 1;
+                } elsif ($hasnoaccess) {
+                    $access = 0; 
+                }
+                return $access;
+            }
+        } else {
+            if ($domdef{$tool}{'default'} ne '') {
+                if ($domdef{$tool}{'default'}) {
+                    $access = 1;
+                } elsif ($domdef{$tool}{'default'} == 0) {
+                    $access = 0;
+                }
+                return $access;
+            }
+        }
+    } else {
+        $access = 1;
+        return $access;
+    }
+}
+
+sub is_advanced_user {
+    my ($udom,$uname) = @_;
+    my %roleshash = &get_my_roles($uname,$udom,'userroles',undef,undef,undef,1);
+    my %allroles;
+    my $is_adv;
+    foreach my $role (keys(%roleshash)) {
+        my ($trest,$tdomain,$trole,$sec) = split(/:/,$role);
+        my $area = '/'.$tdomain.'/'.$trest;
+        if ($sec ne '') {
+            $area .= '/'.$sec;
+        }
+        if (($area ne '') && ($trole ne '')) {
+            my $spec=$trole.'.'.$area;
+            if ($trole =~ /^cr\//) {
+                &custom_roleprivs(\%allroles,$trole,$tdomain,$trest,$spec,$area);
+            } elsif ($trole ne 'gr') {
+                &standard_roleprivs(\%allroles,$trole,$tdomain,$spec,$trest,$area);
+            }
+        }
+    }
+    foreach my $role (keys(%allroles)) {
+        last if ($is_adv);
+        foreach my $item (split(/:/,$allroles{$role})) {
+            if ($item ne '') {
+                my ($privilege,$restrictions)=split(/&/,$item);
+                if ($privilege eq 'adv') {
+                    $is_adv = 1;
+                    last;
+                }
+            }
+        }
+    }
+    return $is_adv;
+}
 
 # ---------------------------------------------- Custom access rule evaluation
 
@@ -4374,7 +4724,6 @@ sub allowed {
     }
     
 # Full access at system, domain or course-wide level? Exit.
-
     if ($thisallowed=~/F/) {
 	return 'F';
     }
@@ -4727,6 +5076,9 @@ sub log_query {
 
 sub update_portfolio_table {
     my ($uname,$udom,$file_name,$query,$group,$action) = @_;
+    if ($group ne '') {
+        $file_name =~s /^\Q$group\E//;
+    }
     my $homeserver = &homeserver($uname,$udom);
     my $queryid=
         &reply("querysend:".$query.':'.&escape($uname.':'.$udom.':'.$group).
@@ -5153,11 +5505,11 @@ sub toggle_coursegroup_status {
 }
 
 sub modify_group_roles {
-    my ($cdom,$cnum,$group_id,$user,$end,$start,$userprivs) = @_;
+    my ($cdom,$cnum,$group_id,$user,$end,$start,$userprivs,$selfenroll,$context) = @_;
     my $url = '/'.$cdom.'/'.$cnum.'/'.$group_id;
     my $role = 'gr/'.&escape($userprivs);
     my ($uname,$udom) = split(/:/,$user);
-    my $result = &assignrole($udom,$uname,$url,$role,$end,$start);
+    my $result = &assignrole($udom,$uname,$url,$role,$end,$start,'',$selfenroll,$context);
     if ($result eq 'ok') {
         &devalidate_getgroups_cache($udom,$uname,$cdom,$cnum);
     }
@@ -5273,7 +5625,8 @@ sub plaintext {
 # ----------------------------------------------------------------- Assign Role
 
 sub assignrole {
-    my ($udom,$uname,$url,$role,$end,$start,$deleteflag)=@_;
+    my ($udom,$uname,$url,$role,$end,$start,$deleteflag,$selfenroll,
+        $context)=@_;
     my $mrole;
     if ($role =~ /^cr\//) {
         my $cwosec=$url;
@@ -5307,11 +5660,15 @@ sub assignrole {
             } else {
                 $refused = 1;
             }
-            if ($refused) { 
-                &logthis('Refused assignrole: '.$udom.' '.$uname.' '.$url.
-                         ' '.$role.' '.$end.' '.$start.' by '.
-	  	         $env{'user.name'}.' at '.$env{'user.domain'});
-                return 'refused';
+            if ($refused) {
+                if (($selfenroll == 1) && ($role eq 'st') && ($udom eq $env{'user.domain'}) && ($uname eq $env{'user.name'})) {
+                    $refused = '';
+                } else {
+                    &logthis('Refused assignrole: '.$udom.' '.$uname.' '.$url.
+                             ' '.$role.' '.$end.' '.$start.' by '.
+	  	             $env{'user.name'}.' at '.$env{'user.domain'});
+                    return 'refused';
+                }
             }
         }
         $mrole=$role;
@@ -5328,6 +5685,7 @@ sub assignrole {
     }
     my $origstart = $start;
     my $origend = $end;
+    my $delflag;
 # actually delete
     if ($deleteflag) {
 	if ((&allowed('dro',$udom)) || (&allowed('dro',$url))) {
@@ -5338,6 +5696,7 @@ sub assignrole {
 # set start and finish to negative values for userrolelog
            $start=-1;
            $end=-1;
+           $delflag = 1;
         }
     }
 # send command
@@ -5346,9 +5705,10 @@ sub assignrole {
     if ($answer eq 'ok') {
 	&userrolelog($role,$uname,$udom,$url,$start,$end);
 # for course roles, perform group memberships changes triggered by role change.
+        &courserolelog($role,$uname,$udom,$url,$origstart,$origend,$delflag,$selfenroll,$context);
         unless ($role =~ /^gr/) {
             &Apache::longroup::group_changes($udom,$uname,$url,$role,$origend,
-                                             $origstart);
+                                             $origstart,$selfenroll,$context);
         }
     }
     return $answer;
@@ -5386,7 +5746,7 @@ sub modifyuser {
     my ($udom,    $uname, $uid,
         $umode,   $upass, $first,
         $middle,  $last,  $gene,
-        $forceid, $desiredhome, $email)=@_;
+        $forceid, $desiredhome, $email, $inststatus)=@_;
     $udom= &LONCAPA::clean_domain($udom);
     $uname=&LONCAPA::clean_username($uname);
     &logthis('Call to modify user '.$udom.', '.$uname.', '.$uid.', '.
@@ -5447,7 +5807,7 @@ sub modifyuser {
 # -------------------------------------------------------------- Add names, etc
     my @tmp=&get('environment',
 		   ['firstname','middlename','lastname','generation','id',
-                    'permanentemail'],
+                    'permanentemail','inststatus'],
 		   $udom,$uname);
     my %names;
     if ($tmp[0] =~ m/^error:.*/) { 
@@ -5465,19 +5825,23 @@ sub modifyuser {
     if (defined($gene))   { $names{'generation'} = $gene; }
     if ($email) {
        $email=~s/[^\w\@\.\-\,]//gs;
-       if ($email=~/\@/) { $names{'notification'} = $email;
-			   $names{'critnotification'} = $email;
-			   $names{'permanentemail'} = $email; }
+       if ($email=~/\@/) { $names{'permanentemail'} = $email; }
     }
     if ($uid) { $names{'id'}  = $uid; }
+    if (defined($inststatus)) { $names{'inststatus'} = $inststatus; } 
     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);
-    &logthis('Success modifying user '.$udom.', '.$uname.', '.$uid.', '.
-             $umode.', '.$first.', '.$middle.', '.
-	     $last.', '.$gene.' by '.
-             $env{'user.name'}.' at '.$env{'user.domain'});
+    my $logmsg = 'Success modifying user '.$udom.', '.$uname.', '.$uid.', '.
+                 $umode.', '.$first.', '.$middle.', '.
+	         $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';
+    }
+    &logthis($logmsg);
     return 'ok';
 }
 
@@ -5485,7 +5849,8 @@ sub modifyuser {
 
 sub modifystudent {
     my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec,
-        $end,$start,$forceid,$desiredhome,$email,$type,$locktype,$cid)=@_;
+        $end,$start,$forceid,$desiredhome,$email,$type,$locktype,$cid,
+        $selfenroll,$context)=@_;
     if (!$cid) {
 	unless ($cid=$env{'request.course.id'}) {
 	    return 'not_in_class';
@@ -5500,12 +5865,12 @@ sub modifystudent {
     # students environment
     $uid = undef if (!$forceid);
     $reply = &modify_student_enrollment($udom,$uname,$uid,$first,$middle,$last,
-					$gene,$usec,$end,$start,$type,$locktype,$cid);
+					$gene,$usec,$end,$start,$type,$locktype,$cid,$selfenroll,$context);
     return $reply;
 }
 
 sub modify_student_enrollment {
-    my ($udom,$uname,$uid,$first,$middle,$last,$gene,$usec,$end,$start,$type,$locktype,$cid) = @_;
+    my ($udom,$uname,$uid,$first,$middle,$last,$gene,$usec,$end,$start,$type,$locktype,$cid,$selfenroll,$context) = @_;
     my ($cdom,$cnum,$chome);
     if (!$cid) {
 	unless ($cid=$env{'request.course.id'}) {
@@ -5563,7 +5928,7 @@ sub modify_student_enrollment {
     if ($usec) {
 	$uurl.='/'.$usec;
     }
-    return &assignrole($udom,$uname,$uurl,'st',$end,$start);
+    return &assignrole($udom,$uname,$uurl,'st',$end,$start,undef,$selfenroll,$context);
 }
 
 sub format_name {
@@ -5682,7 +6047,7 @@ ENDINITMAP
 sub is_course {
     my ($cdom,$cnum) = @_;
     my %courses = &courseiddump($cdom,'.',1,'.','.',$cnum,undef,
-				undef,'.',undef,1);
+				undef,'.');
     if (exists($courses{$cdom.'_'.$cnum})) {
         return 1;
     }
@@ -5692,33 +6057,41 @@ sub is_course {
 # ---------------------------------------------------------- Assign Custom Role
 
 sub assigncustomrole {
-    my ($udom,$uname,$url,$rdom,$rnam,$rolename,$end,$start,$deleteflag)=@_;
+    my ($udom,$uname,$url,$rdom,$rnam,$rolename,$end,$start,$deleteflag,$selfenroll,$context)=@_;
     return &assignrole($udom,$uname,$url,'cr/'.$rdom.'/'.$rnam.'/'.$rolename,
-                       $end,$start,$deleteflag);
+                       $end,$start,$deleteflag,$selfenroll,$context);
 }
 
 # ----------------------------------------------------------------- Revoke Role
 
 sub revokerole {
-    my ($udom,$uname,$url,$role,$deleteflag)=@_;
+    my ($udom,$uname,$url,$role,$deleteflag,$selfenroll,$context)=@_;
     my $now=time;
-    return &assignrole($udom,$uname,$url,$role,$now,$deleteflag);
+    return &assignrole($udom,$uname,$url,$role,$now,undef,$deleteflag,$selfenroll,$context);
 }
 
 # ---------------------------------------------------------- Revoke Custom Role
 
 sub revokecustomrole {
-    my ($udom,$uname,$url,$rdom,$rnam,$rolename,$deleteflag)=@_;
+    my ($udom,$uname,$url,$rdom,$rnam,$rolename,$deleteflag,$selfenroll,$context)=@_;
     my $now=time;
     return &assigncustomrole($udom,$uname,$url,$rdom,$rnam,$rolename,$now,
-           $deleteflag);
+           $deleteflag,$selfenroll,$context);
 }
 
 # ------------------------------------------------------------ Disk usage
 sub diskusage {
-    my ($udom,$uname,$directoryRoot)=@_;
-    $directoryRoot =~ s/\/$//;
-    my $listing=&reply('du:'.$directoryRoot,homeserver($uname,$udom));
+    my ($udom,$uname,$directorypath,$getpropath)=@_;
+    $directorypath =~ s/\/$//;
+    my $listing=&reply('du2:'.&escape($directorypath).':'
+                       .&escape($getpropath).':'.&escape($uname).':'
+                       .&escape($udom),homeserver($uname,$udom));
+    if ($listing eq 'unknown_cmd') {
+        if ($getpropath) {
+            $directorypath = &propath($udom,$uname).'/'.$directorypath; 
+        }
+        $listing = &reply('du:'.$directorypath,homeserver($uname,$udom));
+    }
     return $listing;
 }
 
@@ -5959,20 +6332,18 @@ sub modify_access_controls {
                 }
             }
         }
+        my ($group);
+        if (&is_course($domain,$user)) {
+            ($group,my $file) = split(/\//,$file_name,2);
+        }
         $deloutcome = &del('file_permissions',\@deletions,$domain,$user);
         $new_values{$file_name."\0".'accesscontrol'} = \%new_control;
         $outcome = &put('file_permissions',\%new_values,$domain,$user);
         #  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',
+            &update_portfolio_table($user,$domain,$file_name,'portfolio_access',
                                     $group);
     } else {
         $outcome = "error: could not obtain lockfile\n";  
@@ -6135,30 +6506,49 @@ sub unmark_as_readonly {
 # ------------------------------------------------------------ Directory lister
 
 sub dirlist {
-    my ($uri,$userdomain,$username,$alternateDirectoryRoot)=@_;
-
+    my ($uri,$userdomain,$username,$getpropath,$getuserdir,$alternateRoot)=@_;
     $uri=~s/^\///;
     $uri=~s/\/$//;
     my ($udom, $uname);
-    (undef,$udom,$uname)=split(/\//,$uri);
-    if(defined($userdomain)) {
+    if ($getuserdir) {
         $udom = $userdomain;
-    }
-    if(defined($username)) {
         $uname = $username;
+    } else {
+        (undef,$udom,$uname)=split(/\//,$uri);
+        if(defined($userdomain)) {
+            $udom = $userdomain;
+        }
+        if(defined($username)) {
+            $uname = $username;
+        }
     }
+    my ($dirRoot,$listing,@listing_results);
 
-    my $dirRoot = $perlvar{'lonDocRoot'};
-    if(defined($alternateDirectoryRoot)) {
-        $dirRoot = $alternateDirectoryRoot;
+    $dirRoot = $perlvar{'lonDocRoot'};
+    if (defined($getpropath)) {
+        $dirRoot = &propath($udom,$uname);
         $dirRoot =~ s/\/$//;
+    } elsif (defined($getuserdir)) {
+        my $subdir=$uname.'__';
+        $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
+        $dirRoot = $Apache::lonnet::perlvar{'lonUsersDir'}
+                   ."/$udom/$subdir/$uname";
+    } elsif (defined($alternateRoot)) {
+        $dirRoot = $alternateRoot;
     }
 
     if($udom) {
         if($uname) {
-            my $listing = &reply('ls2:'.$dirRoot.'/'.$uri,
-				 &homeserver($uname,$udom));
-            my @listing_results;
+            $listing = &reply('ls3:'.&escape('/'.$uri).':'.$getpropath.':'
+                              .$getuserdir.':'.&escape($dirRoot)
+                              .':'.&escape($uname).':'.&escape($udom),
+                              &homeserver($uname,$udom));
+            if ($listing eq 'unknown_cmd') {
+                $listing = &reply('ls2:'.$dirRoot.'/'.$uri,
+                                  &homeserver($uname,$udom));
+            } else {
+                @listing_results = map { &unescape($_); } split(/:/,$listing);
+            }
             if ($listing eq 'unknown_cmd') {
                 $listing = &reply('ls:'.$dirRoot.'/'.$uri,
 				  &homeserver($uname,$udom));
@@ -6167,13 +6557,18 @@ sub dirlist {
                 @listing_results = map { &unescape($_); } split(/:/,$listing);
             }
             return @listing_results;
-        } elsif(!defined($alternateDirectoryRoot)) {
+        } elsif(!$alternateRoot) {
             my %allusers;
 	    my %servers = &get_servers($udom,'library');
-	    foreach my $tryserver (keys(%servers)) {
-		my $listing = &reply('ls2:'.$perlvar{'lonDocRoot'}.'/res/'.
-				     $udom, $tryserver);
-		my @listing_results;
+ 	    foreach my $tryserver (keys(%servers)) {
+                $listing = &reply('ls3:'.&escape("/res/$udom").':::::'.
+                                  &escape($udom),$tryserver);
+                if ($listing eq 'unknown_cmd') {
+		    $listing = &reply('ls2:'.$perlvar{'lonDocRoot'}.'/res/'.
+				      $udom, $tryserver);
+                } else {
+                    @listing_results = map { &unescape($_); } split(/:/,$listing);
+                }
 		if ($listing eq 'unknown_cmd') {
 		    $listing = &reply('ls:'.$perlvar{'lonDocRoot'}.'/res/'.
 				      $udom, $tryserver);
@@ -6200,13 +6595,13 @@ sub dirlist {
         } else {
             return ('missing user name');
         }
-    } elsif(!defined($alternateDirectoryRoot)) {
+    } elsif(!defined($getpropath)) {
         my @all_domains = sort(&all_domains());
-         foreach my $domain (@all_domains) {
-             $domain = $perlvar{'lonDocRoot'}.'/res/'.$domain.'/&domain';
-         }
-         return @all_domains;
-     } else {
+        foreach my $domain (@all_domains) {
+            $domain = $perlvar{'lonDocRoot'}.'/res/'.$domain.'/&domain';
+        }
+        return @all_domains;
+    } else {
         return ('missing domain');
     }
 }
@@ -6216,23 +6611,13 @@ sub dirlist {
 # when it was last modified.  It will also return an error of -1
 # if an error occurs
 
-##
-## FIXME: This subroutine assumes its caller knows something about the
-## directory structure of the home server for the student ($root).
-## Not a good assumption to make.  Since this is for looking up files
-## in user directories, the full path should be constructed by lond, not
-## whatever machine we request data from.
-##
 sub GetFileTimestamp {
-    my ($studentDomain,$studentName,$filename,$root)=@_;
+    my ($studentDomain,$studentName,$filename,$getuserdir)=@_;
     $studentDomain = &LONCAPA::clean_domain($studentDomain);
     $studentName   = &LONCAPA::clean_username($studentName);
-    my $subdir=$studentName.'__';
-    $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
-    my $proname="$studentDomain/$subdir/$studentName";
-    $proname .= '/'.$filename;
-    my ($fileStat) = &Apache::lonnet::dirlist($proname, $studentDomain, 
-                                              $studentName, $root);
+    my ($fileStat) = 
+        &Apache::lonnet::dirlist($filename,$studentDomain,$studentName, 
+                                 undef,$getuserdir);
     my @stats = split('&', $fileStat);
     if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') {
         # @stats contains first the filename, then the stat output
@@ -6246,12 +6631,11 @@ sub stat_file {
     my ($uri) = @_;
     $uri = &clutter_with_no_wrapper($uri);
 
-    my ($udom,$uname,$file,$dir);
+    my ($udom,$uname,$file);
     if ($uri =~ m-^/(uploaded|editupload)/-) {
 	($udom,$uname,$file) =
 	    ($uri =~ m-/(?:uploaded|editupload)/?($match_domain)/?($match_name)/?(.*)-);
 	$file = 'userfiles/'.$file;
-	$dir = &propath($udom,$uname);
     }
     if ($uri =~ m-^/res/-) {
 	($udom,$uname) = 
@@ -6263,8 +6647,11 @@ sub stat_file {
 	# unable to handle the uri
 	return ();
     }
-
-    my ($result) = &dirlist($file,$udom,$uname,$dir);
+    my $getpropath;
+    if ($file =~ /^userfiles\//) {
+        $getpropath = 1;
+    }
+    my ($result) = &dirlist($file,$udom,$uname,$getpropath);
     my @stats = split('&', $result);
     
     if($stats[0] ne 'empty' && $stats[0] ne 'no_such_dir') {
@@ -6297,7 +6684,7 @@ sub directcondval {
 	    untie(%bighash);
 	}
 	my $value = &docondval($sub_condition);
-	&appenv('user.state.'.$env{'request.course.id'}.".$number" => $value);
+	&appenv({'user.state.'.$env{'request.course.id'}.".$number" => $value});
 	return $value;
     }
     if ($env{'user.state.'.$env{'request.course.id'}}) {
@@ -6483,7 +6870,7 @@ sub EXT_cache_status {
 sub EXT_cache_set {
     my ($target_domain,$target_user) = @_;
     my $cachename = 'cache.EXT.'.$target_user.'.'.$target_domain;
-    #&appenv($cachename => time);
+    #&appenv({$cachename => time});
 }
 
 # --------------------------------------------------------- Value of a Variable
@@ -7385,7 +7772,7 @@ sub symbread {
         if ($syval) {
 	    #unless ($syval=~/\_\d+$/) {
 		#unless ($env{'form.request.prefix'}=~/\.(\d+)\_$/) {
-		    #&appenv('request.ambiguous' => $thisfn);
+		    #&appenv({'request.ambiguous' => $thisfn});
 		    #return $env{$cache_str}='';
 		#}    
 		#$syval.=$1;
@@ -7437,7 +7824,7 @@ sub symbread {
 	    return $env{$cache_str}=$syval;
         }
     }
-    &appenv('request.ambiguous' => $thisfn);
+    &appenv({'request.ambiguous' => $thisfn});
     return $env{$cache_str}='';
 }
 
@@ -7929,7 +8316,10 @@ sub repcopy_userfile {
     if (-e $transferfile) { return 'ok'; }
     my $request;
     $uri=~s/^\///;
-    $request=new HTTP::Request('GET','http://'.&hostname(&homeserver($cnum,$cdom)).'/raw/'.$uri);
+    my $homeserver = &homeserver($cnum,$cdom);
+    my $protocol = $protocol{$homeserver};
+    $protocol = 'http' if ($protocol ne 'https');
+    $request=new HTTP::Request('GET',$protocol.'://'.&hostname($homeserver).'/raw/'.$uri);
     my $response=$ua->request($request,$transferfile);
 # did it work?
     if ($response->is_error()) {
@@ -7944,15 +8334,18 @@ sub repcopy_userfile {
 
 sub tokenwrapper {
     my $uri=shift;
-    $uri=~s|^http\://([^/]+)||;
+    $uri=~s|^https?\://([^/]+)||;
     $uri=~s|^/||;
     $env{'user.environment'}=~/\/([^\/]+)\.id/;
     my $token=$1;
     my (undef,$udom,$uname,$file)=split('/',$uri,4);
     if ($udom && $uname && $file) {
 	$file=~s|(\?\.*)*$||;
-        &appenv("userfile.$udom/$uname/$file" => $env{'request.course.id'});
-        return 'http://'.&hostname(&homeserver($uname,$udom)).'/'.$uri.
+        &appenv({"userfile.$udom/$uname/$file" => $env{'request.course.id'}});
+        my $homeserver = &homeserver($uname,$udom);
+        my $protocol = $protocol{$homeserver};
+        $protocol = 'http' if ($protocol ne 'https');
+        return $protocol.'://'.&hostname($homeserver).'/'.$uri.
                (($uri=~/\?/)?'&':'?').'token='.$token.
                                '&tokenissued='.$perlvar{'lonHostID'};
     } else {
@@ -7967,7 +8360,10 @@ sub tokenwrapper {
 sub getuploaded {
     my ($reqtype,$uri,$cdom,$cnum,$info,$rtncode) = @_;
     $uri=~s/^\///;
-    $uri = 'http://'.&hostname(&homeserver($cnum,$cdom)).'/raw/'.$uri;
+    my $homeserver = &homeserver($cnum,$cdom);
+    my $protocol = $protocol{$homeserver};
+    $protocol = 'http' if ($protocol ne 'https');
+    $uri = $protocol.'://'.&hostname($homeserver).'/raw/'.$uri;
     my $ua=new LWP::UserAgent;
     my $request=new HTTP::Request($reqtype,$uri);
     my $response=$ua->request($request);
@@ -8009,6 +8405,8 @@ sub filelocation {
     } elsif ($file=~m{^/home/$match_username/public_html/}) {
 	# is a correct contruction space reference
         $location = $file;
+    } elsif ($file =~ m-^\Q$Apache::lonnet::perlvar{'lonTabDir'}\E/-) {
+        $location = $file;
     } elsif ($file=~/^\/*(uploaded|editupload)/) { # is an uploaded file
         my ($udom,$uname,$filename)=
   	    ($file=~m -^/+(?:uploaded|editupload)/+($match_domain)/+($match_name)/+(.*)$-);
@@ -8017,8 +8415,7 @@ sub filelocation {
         my @ids=&current_machine_ids();
         foreach my $id (@ids) { if ($id eq $home) { $is_me=1; } }
         if ($is_me) {
-  	    $location=&propath($udom,$uname).
-  	      '/userfiles/'.$filename;
+  	    $location=&propath($udom,$uname).'/userfiles/'.$filename;
         } else {
   	  $location=$Apache::lonnet::perlvar{'lonDocRoot'}.'/userfiles/'.
   	      $udom.'/'.$uname.'/'.$filename;
@@ -8048,7 +8445,7 @@ sub filelocation {
 
 sub hreflocation {
     my ($dir,$file)=@_;
-    unless (($file=~m-^http://-i) || ($file=~m-^/-)) {
+    unless (($file=~m-^https?\://-i) || ($file=~m-^/-)) {
 	$file=filelocation($dir,$file);
     } elsif ($file=~m-^/adm/-) {
 	$file=~s-^/adm/wrapper/-/-;
@@ -8244,14 +8641,19 @@ sub get_dns {
     open(my $config,"<$perlvar{'lonTabDir'}/hosts.tab");
     foreach my $dns (<$config>) {
 	next if ($dns !~ /^\^(\S*)/x);
-	$alldns{$1} = 1;
+        my $line = $1;
+        my ($host,$protocol) = split(/:/,$line);
+        if ($protocol ne 'https') {
+            $protocol = 'http';
+        }
+	$alldns{$host} = $protocol;
     }
     while (%alldns) {
 	my ($dns) = keys(%alldns);
-	delete($alldns{$dns});
 	my $ua=new LWP::UserAgent;
-	my $request=new HTTP::Request('GET',"http://$dns$url");
+	my $request=new HTTP::Request('GET',"$alldns{$dns}://$dns$url");
 	my $response=$ua->request($request);
+        delete($alldns{$dns});
 	next if ($response->is_error());
 	my @content = split("\n",$response->content);
 	&Apache::lonnet::do_cache_new('dns',$url,\@content,30*24*60*60);
@@ -8316,6 +8718,12 @@ sub get_dns {
 	}
 	return $domain{$name}{$what};
     }
+
+    sub domain_info {
+        &load_domain_tab() if (!$loaded);
+        return %domain;
+    }
+
 }
 
 
@@ -8333,13 +8741,22 @@ sub get_dns {
 	    next if ($configline =~ /^(\#|\s*$ )/x);
 	    next if ($configline =~ /^\^/);
 	    chomp($configline);
-	    my ($id,$domain,$role,$name)=split(/:/,$configline);
+	    my ($id,$domain,$role,$name,$protocol)=split(/:/,$configline);
 	    $name=~s/\s//g;
 	    if ($id && $domain && $role && $name) {
 		$hostname{$id}=$name;
 		push(@{$name_to_host{$name}}, $id);
 		$hostdom{$id}=$domain;
 		if ($role eq 'library') { $libserv{$id}=$name; }
+                if (defined($protocol)) {
+                    if ($protocol eq 'https') {
+                        $protocol{$id} = $protocol;
+                    } else {
+                        $protocol{$id} = 'http'; 
+                    }
+                } else {
+                    $protocol{$id} = 'http';
+                }
 	    }
 	}
     }
@@ -8384,6 +8801,11 @@ sub get_dns {
 	return %name_to_host;
     }
 
+    sub all_host_domain {
+        &load_hosts_tab() if (!$loaded);
+        return %hostdom;
+    }
+
     sub is_library {
 	&load_hosts_tab() if (!$loaded);
 
@@ -8614,6 +9036,7 @@ $memcache=new Cache::Memcached({'servers
 
 $processmarker='_'.time.'_'.$perlvar{'lonHostID'};
 $dumpcount=0;
+$locknum=0;
 
 &logtouch();
 &logthis('<font color="yellow">INFO: Read configuration</font>');
@@ -8781,7 +9204,7 @@ when the connection is brought back up
 =item * B<con_failed>: unable to contact remote host and unable to save message
 for later delivery
 
-=item * B<error:>: an error a occured, a description of the error follows the :
+=item * B<error:>: an error a occurred, a description of the error follows the :
 
 =item * B<no_such_host>: unable to fund a host associated with the user/domain
 that was requested
@@ -8796,10 +9219,12 @@ that was requested
 
 =item * 
 X<appenv()>
-B<appenv(%hash)>: the value of %hash is written to
+B<appenv($hashref,$rolesarrayref)>: the value of %{$hashref} is written to
 the user envirnoment file, and will be restored for each access this
 user makes during this session, also modifies the %env for the current
-process
+process. Optional rolesarrayref - if defined contains a reference to an array
+of roles which are exempt from the restriction on modifying user.role entries 
+in the user's environment.db and in %env.    
 
 =item *
 X<delenv()>
@@ -8929,7 +9354,7 @@ provided for types, will default to retu
 
 =item *
 
-assignrole($udom,$uname,$url,$role,$end,$start) : assign role; give a role to a
+assignrole($udom,$uname,$url,$role,$end,$start,$deleteflag,$selfenroll,$context) : assign role; give a role to a
 user for the level given by URL.  Optional start and end dates (leave empty
 string or zero for "no date")
 
@@ -8946,14 +9371,15 @@ modifyuserauth($udom,$uname,$umode,$upas
 
 =item *
 
-modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene) : 
+modifyuser($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,
+           $forceid,$desiredhome,$email,$inststatus) : 
 modify user
 
 =item *
 
 modifystudent
 
-modify a students enrollment and identification information.
+modify a student's enrollment and identification information.
 The course id is resolved based on the current users environment.  
 This means the envoking user must be a course coordinator or otherwise
 associated with a course.
@@ -8965,25 +9391,25 @@ Inputs:
 
 =over 4
 
-=item B<$udom> Students loncapa domain
+=item B<$udom> Student's loncapa domain
 
-=item B<$uname> Students loncapa login name
+=item B<$uname> Student's loncapa login name
 
-=item B<$uid> Students id/student number
+=item B<$uid> Student/Employee ID
 
-=item B<$umode> Students authentication mode
+=item B<$umode> Student's authentication mode
 
-=item B<$upass> Students password
+=item B<$upass> Student's password
 
-=item B<$first> Students first name
+=item B<$first> Student's first name
 
-=item B<$middle> Students middle name
+=item B<$middle> Student's middle name
 
-=item B<$last> Students last name
+=item B<$last> Student's last name
 
-=item B<$gene> Students generation
+=item B<$gene> Student's generation
 
-=item B<$usec> Students section in course
+=item B<$usec> Student's section in course
 
 =item B<$end> Unix time of the roles expiration
 
@@ -8993,6 +9419,20 @@ Inputs:
 
 =item B<$desiredhome> server to use as home server for student
 
+=item B<$email> Student's permanent e-mail address
+
+=item B<$type> Type of enrollment (auto or manual)
+
+=item B<$locktype> boolean - enrollment type locked to prevent Autoenroll.pl changing manual to auto    
+
+=item B<$cid> courseID - needed if a course role is assigned by a user whose current role is DC
+
+=item B<$selfenroll> boolean - 1 if user role change occurred via self-enrollment
+
+=item B<$context> role change context (shown in User Management Logs display in a course)
+
+=item B<$inststatus> institutional status of user - : separated string of escaped status types  
+
 =back
 
 =item *
@@ -9026,6 +9466,16 @@ Inputs:
 
 =item $start
 
+=item $type
+
+=item $locktype
+
+=item $cid
+
+=item $selfenroll
+
+=item $context
+
 =back
 
 
@@ -9334,7 +9784,7 @@ Returns:
  'key_exists: <key>' -> failed to anything out of $storehash, as at
                         least <key> already existed in the db (other
                         requested keys may also already exist)
- 'error: <msg>' -> unable to tie the DB or other erorr occured
+ 'error: <msg>' -> unable to tie the DB or other error occurred
  'con_lost' -> unable to contact request server
  'refused' -> action was not allowed by remote machine
 
@@ -9362,6 +9812,18 @@ put_dom($namespace,$storehash,$udom,$uho
 domain level either on specified domain server ($uhome) or primary domain 
 server ($udom and $uhome are optional)
 
+=item * 
+
+get_domain_defaults($target_domain) : returns hash with defaults for
+authentication and language in the domain. Keys are: auth_def, auth_arg_def,
+lang_def; corresponsing values are authentication type (internal, krb4, krb5,
+or localauth), initial password or a kerberos realm, language (e.g., en-us).
+Values are retrieved from cache (if current), or from domain's configuration.db
+(if available), or lastly from values in lonTabs/dns_domain,tab, 
+or lonTabs/domain.tab. 
+
+%domdefaults = &get_auth_defaults($target_domain);
+
 =back
 
 =head2 Network Status Functions