--- loncom/Attic/lchtmldir	2002/05/03 03:21:25	1.2
+++ loncom/Attic/lchtmldir	2005/06/21 11:00:21	1.17
@@ -41,7 +41,7 @@
 #    NSCL
 #    Michigan State University8
 #    East Lansing, MI 48824-1321
-
+#
 #   General flow of control:
 #   1. Validate process state (must be run as www).
 #   2. Validate parameters:  Need two parameters:
@@ -61,17 +61,21 @@
 #       - internal - www:www/2775
 #       - local    - www:www/2775
 #
-
+#
 #
 #   Take a few precautions to be sure that we're not vulnerable to trojan
 #   horses and other fine issues:
 #
 use strict; 
+use Fcntl qw(:mode);
+use DirHandle;
+use POSIX;
 
 $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/sbin:/home/httpd/perl';
 delete @ENV{qw{IFS CDPATH ENV BASH_ENV}};
 
-my $DEBUG = 0;                         # .nonzero -> Debug printing enabled.
+my $DEBUG = 1;                         # .nonzero -> Debug printing enabled.
+my $path_sep = "/";		# Unix like operating systems.
 
 
 # If the UID of the running process is not www exit with error.
@@ -117,7 +121,7 @@ if($DEBUG) {
 
 if( $authentication ne "unix:"     &&
     $authentication ne "internal:" &&
-    $authentication ne "krb4:"     &&
+    $authentication !~ /^krb(4|5):(.*)/ &&
     $authentication ne "localauth:") {
     if($DEBUG) {
 	print("Invalid authentication parameter: ".$authentication."\n");
@@ -140,7 +144,7 @@ my $safeuser = $patt;
 if($DEBUG) {
     print("Save username = $safeuser \n");
 }
-if(($username ne $safeuser) or ($safeuser!~/^[A-za-z]/)) {
+if(($username ne $safeuser) or ($safeuser!~/^[A-z]/)) {
     if($DEBUG) {
 	print("User name $username had illegal characters\n");
     }
@@ -167,7 +171,9 @@ if(($dirtry1 ne $dir) or ($dirtry2 ne $d
 
 # As root, create the directory.
 
-my $fulldir = $dirtry1."/public_html";
+my $homedir = $dirtry1;
+my $fulldir = $homedir."/public_html";
+
 if($DEBUG) {
     print("Full directory path is: $fulldir \n");
 }
@@ -175,11 +181,33 @@ if(!( -e $dirtry1)) {
     if($DEBUG) {
 	print("User's home directory $dirtry1 does not exist\n");
     }
-    exit 6;
+    if ($authentication eq "unix:") {
+        exit 6;
+    }
+}
+if ($authentication eq "unix:") {
+    # check whether group $safeuser exists.
+    my $usergroups = `id -nG $safeuser`;
+    if (! grep /^$safeuser$/, split(/\s+/,$usergroups)) { 
+        if($DEBUG) {
+            print("Group \"$safeuser\" does not exist or $safeuser is not a member of that group.\n");
+        }
+        exit 7;
+    }
 }
+
+
+
 &EnableRoot;
 
-&System("/bin/mkdir $fulldir")   unless (-e $fulldir);
+#  If authentication is internal and the top level directory exists
+#  give it the right permissions (in case this is a modification.
+
+if ($authentication eq "internal:") {
+    chmod(0711, $homedir);	# so www can enter ~/public_html.
+}
+
+&System("/bin/mkdir -p $fulldir")   unless (-e $fulldir);
     unless(-e $fulldir."/index.html") {
 	open OUT,">".$fulldir."/index.html";
 	print OUT<<END;
@@ -187,55 +215,70 @@ if(!( -e $dirtry1)) {
 	<head>
 	<title>$safeuser</title>
         </head>
-        <body>
-        <h1>$safeuser</h1>
+        <body bgcolor="#ccffdd">
+        <h1>$safeuser Construction Space</h1>
+          <h2>
+            The Learning<i>Online</i> Network with Computer-Assisted Personalized Approach
+          </h2>
           <p>
-            Learning Online Network
+This is your construction space within LON-CAPA, where you would construct resources which are meant to be
+used across courses and institutions.
           </p>
           <p>
-            This area provides for:
+Material within this area can only be seen and edited by $safeuser and designated co-authors. To make
+it available to students and other instructors, the material needs to be published.
           </p>
-          <ul>
-             <li>resource construction</li>
-             <li>resource publication</li>
-             <li>record-keeping</li>
-          </ul>
         </body>
        </html>
 END
     close OUT;
     }
-&System("/bin/chmod  02775  $fulldir");
-&System("/bin/chmod  0775  $fulldir"."/index.html");
+
+&System("/bin/chmod  02770  $fulldir");
+&System("/bin/chmod  0770  $fulldir"."/index.html");
 
 
 # Based on the authentiation mode, set the ownership of the directory.
 
 if($authentication eq "unix:") {	# Unix mode authentication...
-    
-   
-    &System("/bin/chown -R   $username".":".$username." ".$fulldir);
-    &JoinGroup($username);
-
-
-}
-elsif ($authentication eq "internal:") { # Internal authentication.
-
-    &System("/bin/chown -R www:www  $fulldir");
-}
-elsif ($authentication eq "krb4:") { # Kerberos version 4 authentication
-    &System("/bin/chown -R $username".':'.$username." ".$fulldir);
-    &JoinGroup($username);
-}
-elsif ($authentication eq "localauth:") { # Local authentiation
-    &System("/bin/chown -R  $username".':'.$username."  $fulldir");
-}
-else {
-    if($DEBUG) {
-	print("Authentication not legal".$authentication);
+    print "Unix auth\n";
+    &System("/bin/chown -R   $safeuser:$safeuser"." ".$fulldir);
+    &JoinGroup($safeuser);
+} else {
+    # Internal, Kerberos, and Local authentication are for users
+    # who do not have unix accounts on the system.  Therefore we
+    # will give ownership of their public_html directories to www:www
+    # If the user is an internal auth user, the rest of the directory tree
+    # gets owned by root.  This chown is needed in case what's really happening
+    # is that a file system user is being demoted to internal user...
+
+    if($authentication eq "internal:") {
+	#  In case the user was a unix/filesystem authenticated user,
+	#  we'll take a bit of time here to write  a script in the
+	#  user's home directory that can reset ownerships and permissions
+	#  back the way the used to be.
+
+	# This can take long enough for lond to time out, so we'll do it
+	# in a separate process that we'll not wait for.
+	#
+	my $fpid = fork;
+	if($fpid) {
+	    &DisableRoot;
+	    exit 0;
+	} else {
+	    print "Forked\n";
+	    POSIX::setsid();	# Disassociate from parent.
+	    print "Separate session\n";
+	    &write_restore_script($homedir);
+	    print "Restore script written\n";
+	    &System("/bin/chown -R root:root ".$homedir);
+	    &System("/bin/chown -R www:www  ".$fulldir);
+	    print "Exiting\n";
+	    exit 0;
+	}
+    } else {
+	&System("/bin/chown -R www:www  ".$fulldir);
     }
-    &DisableRoot;
-    exit 5;
 
 }
 &DisableRoot;
@@ -261,7 +304,7 @@ sub EnableRoot {
 	# root capability is already enabled
     }
     if($DEBUG) {
-	print("Enable Root - id =  $> \n");
+	print("Enable Root - id =  $> $<\n");
     }
     return $>;  
 }
@@ -278,11 +321,17 @@ sub DisableRoot {
 	print("Disable root: id = ".$>."\n");
     }
 }
-
+#
+#  Join the www user to the user's group.
+#  we must be running with euid as root at this time.
+#
 sub JoinGroup {
     my $usergroup = shift;
 
     my $groups = `/usr/bin/groups www`;
+    # untaint
+    my ($safegroups)=($groups=~/:\s+([\s\w]+)/);
+    $groups=$safegroups;
     chomp $groups; $groups=~s/^\S+\s+\:\s+//;
     my @grouplist=split(/\s+/,$groups);
     my @ugrouplist=grep {!/www|$usergroup/} @grouplist;
@@ -294,17 +343,157 @@ sub JoinGroup {
 	}
 	exit 6;
     }
-    
+    if (-e '/var/run/httpd.pid') {
+	open(PID,'/var/run/httpd.pid');
+	my $pid=<PID>;
+	close(PID);
+	my ($safepid) = $pid=~ /(\d+)/;
+	$pid = $safepid;
+	if ($pid) {
+	    my $status = system("kill -USR1 $safepid");
+	}
+    }
 }
 
 
 
 sub System {
-    my $command = shift;
+    my ($command,@args) = @_;
     if($DEBUG) {
-	print("system: $command \n");
+	print("system: $command with args ".join(' ',@args)."\n");
     }
-    system($command);
+    system($command,@args);
+}
+
+
+
+
+
+#
+#   This file contains code to recursively process
+#   a Directory.  This is a bit more powerful
+#   than File::Find in that we pass the full
+#   stat info to the processing function.
+#     For each file in the specified directory subtree, 
+#   The user's Code reference is invoked for all files, regular and otherwise
+#   except:
+#      ., ..
+#
+#  Parameters:
+#     code_ref    - Code reference, invoked for each file in the tree.
+#                   as follows:  CodeRef(directory, name, statinfo)
+#                   directory the path to the directory holding the file.
+#                   name      the name of the file within Directory.
+#                   statinfo  a reference to the stat of the file.
+#     start_dir   - The starting point of the directory walk.
+#
+# NOTE:
+#   Yes, we could have just used File::Find, but since we have to get the
+#   stat anyway, this is actually simpler, as File::Find would have gotten
+#   the stat to figure out the file type and then we would have gotten it
+#   again.
+#
+
+sub process_tree {
+    my ($code_ref, $start_dir)  = @_;
+
+    my $dir = new DirHandle $start_dir; 
+    if (!defined($dir)) {
+        print "Failed to  open dirhandle: $start_dir\n";
+    }
+
+    # Now iterate through this level of the tree:
+
+    while (defined (my $name = $dir->read)) {
+	next if $name =~/^\.\.?$/;       # Skip ., .. (see cookbook pg 319)
+	
+	my $full_name   = $start_dir.$path_sep.$name; # Full filename path.
+	my @stat_info  = lstat($full_name);
+	my $mode       = $stat_info[2];
+	my $type       = $mode & 0170000; #  File type.
+
+	# Unless the file type is a symlink, call the user code:
+
+	unless ($type == S_IFLNK) {
+	    &$code_ref($start_dir, $name, \@stat_info);
+	}
+
+	# If the entry is a directory, we need to recurse:
+
+
+	if (($type ==  S_IFDIR) != 0) {
+	    &process_tree($code_ref, $full_name);
+	}
+    }
+
+}
+#
+#   Callback from process_tree to write the script lines
+#   requried to restore files to current ownership and permission.
+# Parameters:
+#    dir         - Name of the directory the file lives in.
+#    name        - Name of the file itself.
+#    statinfo    - Array from lstat called on the file.
+#
+#
+sub write_script {
+    my ($dir, $name, $statinfo) = @_;
+
+    my $fullname = $dir.$path_sep.$name;
+
+    #  We're going to '' the name, but we need to deal with embedded
+    #  ' characters.  Using " is much worse as we'd then have to
+    #  escape all the shell escapes too.  This way all we need
+    #  to do is replace ' with '\''
+
+    $fullname =~ s/\'/\'\\\'\'/g;
+
+    my $perms    = $statinfo->[2] & 0777; # Just permissions.
+    printf CHMODSCRIPT "chmod 0%o '%s'\n", $perms, $fullname;
+    printf CHMODSCRIPT "chown %d:%d '%s'\n", $statinfo->[4], $statinfo->[5], 
+                                         $fullname
+
+
+}
+# 
+#    Write a script in the user's home directory that can restore
+#    the permissions and ownerhips of all the files in the directory
+#    tree to their current ownerships and permissions.  This is done
+#    prior to making the user into an internally authenticated user
+#    in case they were previously file system authenticated and
+#    need to go back.
+#      The file we will create will be of the form
+#        restore_n.sh  Where n is a number that we will keep
+#   incrementing as needed until there isn't a file by that name.
+#   
+# Parameters:
+#    dir      - Path to the user's home directory.
+#
+sub write_restore_script {
+    my ($dir)   = @_;
+
+    #   Create a unique file:
+
+    my $version_number     = 0;
+    my $filename           = 'restore_'.$version_number.'.sh';
+    my $full_name           = $dir.$path_sep.$filename;
+
+    while(-e $full_name) {
+	$version_number++;
+	$filename         = 'restore_'.$version_number.'.sh';
+	$full_name        = $dir.$path_sep.$filename;
+    }
+    # $full_name is the full path of a file that does not yet exist
+    # of the form we want:
+
+    open(CHMODSCRIPT, "> $full_name");
+
+    &process_tree(\&write_script, $dir);
+
+    close(CHMODSCRIPT);
+
+    chmod(0750, $full_name);
+
 }