--- loncom/Attic/lcuseradd	2000/10/30 03:30:26	1.13
+++ loncom/Attic/lcuseradd	2004/08/05 20:47:27	1.27
@@ -1,26 +1,66 @@
 #!/usr/bin/perl
+
+# The Learning Online Network with CAPA
+#
+# lcuseradd - LON-CAPA setuid script to coordinate all actions
+#             with adding a user with filesystem privileges (e.g. author)
 #
-# lcuseradd
 #
-# Scott Harrison
-# SH: October 27, 2000
-# SH: October 29, 2000
+# $Id: lcuseradd,v 1.27 2004/08/05 20:47:27 albertel Exp $
+###
+
+###############################################################################
+##                                                                           ##
+## ORGANIZATION OF THIS PERL SCRIPT                                          ##
+##                                                                           ##
+## 1. Description of script                                                  ##
+## 2. Invoking script (standard input)                                       ##
+## 3. Example usage inside another piece of code                             ##
+## 4. Description of functions                                               ##
+## 5. Exit codes                                                             ##
+## 6. Initializations                                                        ##
+## 7. Make sure this process is running from user=www                        ##
+## 8. Start running script with www permissions                              ##
+## 9. Handle case of another lcpasswd process (locking)                      ##
+## 10. Error-check input, need 3 values (user name, password 1, password 2)  ##
+## 11. Start running script with root permissions                            ##
+## 12. Add user and make www a member of the user-specific group             ##
+## 13. Set password                                                          ##
+## 14. Make final modifications to the user directory                        ##
+## 15. Exit script (unlock)                                                  ##
+##                                                                           ##
+###############################################################################
 
 use strict;
 
+# ------------------------------------------------------- Description of script
+#
 # This script is a setuid script that should
-# be run by user 'www'.  It creates a /home/USERNAME directory
-# as well as a /home/USERNAME/public_html directory.
-# It adds user entries to
-# /etc/passwd and /etc/groups.
+# be run by user 'www'.  It creates a /home/USERNAME directory.
+# It adds a user to the unix system.
 # Passwords are set with lcpasswd.
 # www becomes a member of this user group.
 
-# Standard input usage
+# -------------- Invoking script (standard input versus command-line arguments)
+#                Otherwise sensitive information will be available to ps-ers for
+#                a small but exploitable time window.
+#
+# Standard input (STDIN) usage
 # First line is USERNAME
 # Second line is PASSWORD
 # Third line is PASSWORD
-
+# Fouth line is the name of a file to which an error code will be written.
+#            If the fourth line is omitted, no error file will be written.
+#            In either case, the program Exits with the code as its Exit status.
+#            The error file will just be a single line containing an
+#            error code.
+#            
+#  
+#
+# Command-line arguments [USERNAME] [PASSWORD] [PASSWORD]
+# Yes, but be very careful here (don't pass shell commands)
+# and this is only supported to allow perl-system calls.
+#
 # Valid passwords must consist of the
 # ascii characters within the inclusive
 # range of 0x20 (32) to 0x7E (126).
@@ -28,243 +68,272 @@ use strict;
 # SPACE and
 # !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO
 # PQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
-
+#
 # Valid user names must consist of ascii
 # characters that are alphabetical characters
 # (A-Z,a-z), numeric (0-9), or the underscore
 # mark (_). (Essentially, the perl regex \w).
+# User names must begin with an alphabetical character
+# (A-Z,a-z).
 
-# Command-line arguments [USERNAME] [PASSWORD] [PASSWORD]
-# Yes, but be very careful here (don't pass shell commands)
-# and this is only supported to allow perl-system calls.
-
+# ---------------------------------- Example usage inside another piece of code
 # Usage within code
 #
-# $exitcode=system("/home/httpd/perl/lcuseradd","NAME","PASSWORD1","PASSWORD2")/256;
-# print "uh-oh" if $exitcode;
+# $Exitcode=
+#      system("/home/httpd/perl/lcuseradd","NAME","PASSWORD1","PASSWORD2")/256;
+# print "uh-oh" if $Exitcode;
+
+# ---------------------------------------------------- Description of functions
+# enable_root_capability() : have setuid script run as root
+# disable_root_capability() : have setuid script run as www
+# try_to_lock() : make sure that another lcpasswd process isn't running
 
-# These are the exit codes.
+# ------------------------------------------------------------------ Exit codes
+# These are the Exit codes.
 # ( (0,"ok"),
 # (1,"User ID mismatch.  This program must be run as user 'www'"),
-# (2,"Error. This program needs 3 command-line arguments (username, password 1, password 2)."),
+# (2,"Error. This program needs 3 command-line arguments (username, ".
+#    "password 1, password 2)."),
 # (3,"Error. Three lines should be entered into standard input."),
-# (4,"Error. Too many other simultaneous password change requests being made."),
+# (4,"Error. Too many other simultaneous password change requests being ".
+#    "made."),
 # (5,"Error. User $username does not exist."),
 # (6,"Error. Could not make www a member of the group \"$safeusername\"."),
 # (7,"Error. Root was not successfully enabled.),
-# (8,"Error. Cannot open /etc/passwd."),
+# (8,"Error. Cannot set password."),
 # (9,"Error. The user name specified has invalid characters."),
 # (10,"Error. A password entry had an invalid character."),
 # (11,"Error. User already exists.),
-# (12,"Error. Something went wrong with the addition of user \"$safeusername\"."),
+# (12,"Error. Something went wrong with the addition of user ".
+#     "\"$safeusername\"."),
 # (13,"Error. Password mismatch."),
+# (14, "Error filename is invalid")
 
+# ------------------------------------------------------------- Initializations
 # Security
-$ENV{'PATH'}=""; # Nullify path information.
-$ENV{'BASH_ENV'}=""; # Nullify shell environment information.
+$ENV{'PATH'}='/bin/:/usr/bin:/usr/local/sbin:/home/httpd/perl'; # Nullify path
+                                                                # information
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # nullify potential taints
+
+# Do not print error messages.
+my $noprint=1;
 
-# Do not print error messages if there are command-line arguments
-my $noprint=0;
-if (@ARGV) {
-    $noprint=1;
-}
-
-# Read in /etc/passwd, and make sure this process is running from user=www
-open (IN, "</etc/passwd");
-my @lines=<IN>;
-close IN;
-my $wwwid;
-for my $l (@lines) {
-    chop $l;
-    my @F=split(/\:/,$l);
-    if ($F[0] eq 'www') {$wwwid=$F[2];}
-}
-if ($wwwid!=$<) {
-    print("User ID mismatch.  This program must be run as user 'www'\n") unless $noprint;
-    exit 1;
+#  Error file:
+
+my $error_file;			# This is either the error file name or undef.
+
+print "In lcuseradd\n" unless $noprint;
+
+# ----------------------------- Make sure this process is running from user=www
+my $wwwid=getpwnam('www');
+&disable_root_capability;
+if ($wwwid!=$>) {
+    print("User ID mismatch.  This program must be run as user 'www'\n")
+	unless $noprint;
+    &Exit(1);
 }
+
+# ----------------------------------- Start running script with www permissions
 &disable_root_capability;
 
-# Handle case of another lcpasswd process
+# --------------------------- Handle case of another lcpasswd process (locking)
 unless (&try_to_lock("/tmp/lock_lcpasswd")) {
-    print "Error. Too many other simultaneous password change requests being made.\n" unless $noprint;
-    exit 4;
+    print "Error. Too many other simultaneous password change requests being ".
+	"made.\n" unless $noprint;
+    &Exit(4);
 }
 
-# Gather input.  Should be 3 values (user name, password 1, password 2).
+# ------- Error-check input, need 3 values (user name, password 1, password 2).
 my @input;
-if (@ARGV==3) {
+if (@ARGV>=3) {
     @input=@ARGV;
 }
 elsif (@ARGV) {
-    print("Error. This program needs 3 command-line arguments (username, password 1, password 2).\n") unless $noprint;
+    print("Error. This program needs at least 3 command-line arguments (username, ".
+	  "password 1, password 2 [errorfile]).\n") unless $noprint;
     unlink('/tmp/lock_lcpasswd');
-    exit 2;
+    &Exit(2);
 }
 else {
     @input=<>;
-    if (@input!=3) {
-	print("Error. Three lines should be entered into standard input.\n") unless $noprint;
+    if (@input < 3) {
+	print("Error. At least three lines should be entered into standard input.\n")
+	    unless $noprint;
 	unlink('/tmp/lock_lcpasswd');
-	exit 3;
+	&Exit(3);
     }
-    map {chop} @input;
+    foreach (@input) {chomp;}
 }
 
-my ($username,$password1,$password2)=@input;
+my ($username,$password1,$password2, $error_file)=@input;
+print "Username = ".$username."\n" unless $noprint;
 $username=~/^(\w+)$/;
+print "Username after substitution - ".$username unless $noprint;
 my $safeusername=$1;
-if ($username ne $safeusername) {
-    print "Error. The user name specified has invalid characters.\n";
+print "Safe username = $safeusername \n" unless $noprint;
+
+if (($username ne $safeusername) or ($safeusername!~/^[A-Za-z]/)) {
+    print "Error. The user name specified $username $safeusername  has invalid characters.\n"
+	unless $noprint;
     unlink('/tmp/lock_lcpasswd');
-    exit 9;
+    &Exit(9);
 }
 my $pbad=0;
-map {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}} (split(//,$password1));
-map {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}} (split(//,$password2));
+foreach (split(//,$password1)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
+foreach (split(//,$password2)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
 if ($pbad) {
-    print "Error. A password entry had an invalid character.\n";
+    print "Error. A password entry had an invalid character.\n" unless $noprint;
     unlink('/tmp/lock_lcpasswd');
-    exit 10;
+    &Exit(10);
+}
+
+#
+#   Safe the filename.  For our case, it must only have alpha, numeric, period
+#   and path sparators..
+#
+
+print "Error file is $error_file \n" unless $noprint;
+
+if($error_file) {
+    if($error_file =~ /^([(\w)(\d)\.\/]+)$/) {
+	print "Error file matched pattern $error_file : $1\n" unless $noprint;
+	my $safe_error_file = $1;	# Untainted I think.
+	print "Error file after transform $safe_error_file\n"
+	    unless $noprint;
+	if($error_file == $safe_error_file) {
+	    $error_file = $safe_error_file; # untainted error_file.
+	} else {
+	    $error_file ="";
+	    print "Invalid error filename\n" unless $noprint;
+	    Exit(14);
+	}
+
+    } 
+    else {
+	$error_file="";
+	print "Invalid error filename\n" unless $noprint;
+	Exit(14);
+    }
 }
 
-# Only add user if we can create a brand new home directory (/home/username).
+
+# -- Only add user if we can create a brand new home directory (/home/username)
 if (-e "/home/$safeusername") {
     print "Error. User already exists.\n" unless $noprint;
     unlink('/tmp/lock_lcpasswd');
-    exit 11;
+    &Exit(11);
 }
 
-# Only add user if the two password arguments match.
+# -- Only add user if the two password arguments match.
+
 if ($password1 ne $password2) {
     print "Error. Password mismatch.\n" unless $noprint;
     unlink('/tmp/lock_lcpasswd');
-    exit 13;
+    &Exit(13);
 }
-
+print "enabling root\n" unless $noprint;
+# ---------------------------------- Start running script with root permissions
 &enable_root_capability;
 
-# Add user entry to /etc/passwd and /etc/groups in such
-# a way that www is a member of the user-specific group
+# ------------------- Add user and make www a member of the user-specific group
+# -- Add user
 
-if (system('/usr/sbin/useradd','-c','LON-CAPA user',$safeusername)) {
-    print "Error.  Something went wrong with the addition of user \"$safeusername\".\n" unless $noprint;
+print "adding user: $safeusername \n" unless $noprint;
+my $status = system('/usr/sbin/useradd','-c','LON-CAPA user',$safeusername);
+if ($status) {
+    print "Error.  Something went wrong with the addition of user ".
+	  "\"$safeusername\".\n" unless $noprint;
+    print "Final status of useradd = $status";
     unlink('/tmp/lock_lcpasswd');
-    exit 12;
+    &Exit(12);
 }
-
+print "Done adding user\n" unless $noprint;
 # Make www a member of that user group.
-if (system('/usr/sbin/usermod','-G',$safeusername,'www')) {
-    print "Error. Could not make www a member of the group \"$safeusername\".\n" unless $noprint;
+my $groups=`/usr/bin/groups www` or &Exit(6);
+# untaint
+my ($safegroups)=($groups=~/([\s\w]+)/);
+$groups=$safegroups;
+chomp $groups; $groups=~s/^\S+\s+\:\s+//;
+my @grouplist=split(/\s+/,$groups);
+my @ugrouplist=grep {!/www|$safeusername/} @grouplist;
+my $gl=join(',',(@ugrouplist,$safeusername));
+print "Putting user in its own group\n" unless $noprint;
+if (system('/usr/sbin/usermod','-G',$gl,'www')) {
+    print "Error. Could not make www a member of the group ".
+	  "\"$safeusername\".\n" unless $noprint;
     unlink('/tmp/lock_lcpasswd');
-    exit 6;
+    &Exit(6);
 }
 
-# Set password with lcpasswd-style algorithm (which creates smbpasswd entry).
-# I cannot place a direct shell call to lcpasswd since I have to allow
-# for crazy characters in the password, and the setuid environment of perl
-# requires me to make everything safe.
-
-# Grab the line corresponding to username
-open (IN, "</etc/passwd");
-@lines=<IN>;
-close IN;
-my ($userid,$useroldcryptpwd);
-my @F; my @U;
-for my $l (@lines) {
-    chop $l;
-    @F=split(/\:/,$l);
-    if ($F[0] eq $username) {($userid,$useroldcryptpwd)=($F[2],$F[1]); @U=@F;}
-}
-
-# Verify existence of user
-if (!defined($userid)) {
-    print "Error. User $username does not exist.\n" unless $noprint;
-    unlink('/tmp/lock_lcpasswd');
-    exit 5;
-}
+# ---------------------------------------------------------------- Set password
+# Set password with lcpasswd (which creates smbpasswd entry).
 
-# Construct new password entry (random salt)
-my $newcryptpwd=crypt($password1,(join '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64]));
-$U[1]=$newcryptpwd;
-my $userline=join(':',@U);
-my $rootid=&enable_root_capability;
-if ($rootid!=0) {
-    print "Error.  Root was not successfully enabled.\n" unless $noprint;
-    unlink('/tmp/lock_lcpasswd');
-    exit 7;
-}
-open PASSWORDFILE, '>/etc/passwd' or (print("Error.  Cannot open /etc/passwd.\n") && unlink('/tmp/lock_lcpasswd') && exit(8));
-for my $l (@lines) {
-    @F=split(/\:/,$l);
-    if ($F[0] eq $username) {print PASSWORDFILE "$userline\n";}
-    else {print PASSWORDFILE "$l\n";}
-}
-close PASSWORDFILE;
-
-($>,$<)=(0,0); # fool smbpasswd here to think this is not a setuid environment
-unless (-e '/etc/smbpasswd') {
-    open (OUT,'>/etc/smbpasswd'); close OUT;
-}
-my $smbexist=0;
-open (IN, '</etc/smbpasswd');
-my @lines=<IN>;
-close IN;
-for my $l (@lines) {
-    chop $l;
-    my @F=split(/\:/,$l);
-    if ($F[0] eq $username) {$smbexist=1;}
-}
-unless ($smbexist) {
-    open(OUT,'>>/etc/smbpasswd');
-    print OUT join(':',($safeusername,$userid,'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX','','/home/'.$safeusername,'/bin/bash')) . "\n";
-    close OUT;
-}
-open(OUT,"|/usr/bin/smbpasswd -s $safeusername>/dev/null");
-print OUT $password1; print OUT "\n";
-print OUT $password1; print OUT "\n";
+unlink('/tmp/lock_lcpasswd');
+&disable_root_capability;
+($>,$<)=($wwwid,$wwwid);
+print "Opening lcpasswd pipeline\n" unless $noprint;
+open OUT,"|/home/httpd/perl/lcpasswd";
+print OUT $safeusername;
+print OUT "\n";
+print OUT $password1;
+print OUT "\n";
+print OUT $password1;
+print OUT "\n";
 close OUT;
-$<=$wwwid; # unfool the program
+if ($?) {
+    print "abnormal Exit from close lcpasswd\n" unless $noprint;
+    &Exit(8);
+}
+($>,$<)=($wwwid,0);
+&enable_root_capability;
+
+# -- Don't add public_html... that can be added either by the user
+#    or by lchtmldir when the user is granted an authorship role.
 
-# Make final modifications to the user directory.
-# Add a public_html file with a stand-in index.html file.
+# ------------------------------ Make final modifications to the user directory
+# -- Add a public_html file with a stand-in index.html file
 
-system('/bin/chmod','-R','0660',"/home/$safeusername");
-system('/bin/chmod','0770',"/home/$safeusername");
-mkdir "/home/$safeusername/public_html",0770;
+ system('/bin/chmod','-R','0660',"/home/$safeusername");
+system('/bin/chmod','0710',"/home/$safeusername");
+mkdir "/home/$safeusername/public_html",0755;
+system('/bin/chmod','02770',"/home/$safeusername/public_html");
 open OUT,">/home/$safeusername/public_html/index.html";
 print OUT<<END;
-<HTML>
-<HEAD>
-<TITLE>$safeusername</TITLE>
-</HEAD>
-<BODY>
-<H1>$safeusername</H1>
-<P>
-Learning Online Network
-</P>
-<P>
-This area provides for:
-</P>
-<UL>
-<LI>resource construction
-<LI>resource publication
-<LI>record-keeping
-</UL>
-</BODY>
-</HTML>
+<html>
+<head>
+<title>$safeusername</title>
+</head>
+<body>
+<h1>Construction Space</h1>
+<h3>$safeusername</h3>
+</body>
+</html>
 END
 close OUT;
+
+print "lcuseradd ownership\n" unless $noprint;
 system('/bin/chown','-R',"$safeusername:$safeusername","/home/$safeusername");
+# ---------------------------------------------------- Gracefull Apache Restart
+if (-e '/var/run/httpd.pid') {
+    print "lcuseradd Apache restart\n" unless $noprint;
+    open(PID,'/var/run/httpd.pid');
+    my $pid=<PID>;
+    close(PID);
+    my ($safepid)=($pid=~s/(\D+)//g);
+    if ($pid) {
+	system('kill','-USR1',"$safepid");
+    }
+}
+# -------------------------------------------------------- Exit script
+print "lcuseradd Exiting\n" unless $noprint;
 &disable_root_capability;
-unlink('/tmp/lock_lcpasswd');
-exit 0;
+&Exit(0);
 
-# ----------------------------------------------------------- have setuid script run as root
+# ---------------------------------------------- Have setuid script run as root
 sub enable_root_capability {
     if ($wwwid==$>) {
-	($<,$>)=($>,$<);
-	($(,$))=($),$();
+	($<,$>)=($>,0);
+	($(,$))=($),0);
     }
     else {
 	# root capability is already enabled
@@ -272,7 +341,7 @@ sub enable_root_capability {
     return $>;
 }
 
-# ----------------------------------------------------------- have setuid script run as www
+# ----------------------------------------------- Have setuid script run as www
 sub disable_root_capability {
     if ($wwwid==$<) {
 	($<,$>)=($>,$<);
@@ -283,7 +352,7 @@ sub disable_root_capability {
     }
 }
 
-# ----------------------------------- make sure that another lcpasswd process isn't running
+# ----------------------- Make sure that another lcpasswd process isn't running
 sub try_to_lock {
     my ($lockfile)=@_;
     my $currentpid;
@@ -320,3 +389,48 @@ sub try_to_lock {
     close LOCK;
     return 1;
 }
+#-------------------------- Exit...
+#
+#   Write the file if the error_file is defined.  Regardless
+#   Exit with the status code.
+#
+sub Exit {
+    my ($code) = @_;		# Status code.
+
+    print "Exiting with status $code error file is $error_file\n" unless $noprint;
+    if($error_file) {
+	open(FH, ">$error_file");
+	print FH  "$code\n";
+	close(FH);
+    }
+    exit $code;
+}
+
+=head1 NAME
+
+lcuseradd - LON-CAPA setuid script to coordinate all actions
+            with adding a user with filesystem privileges (e.g. author)
+
+=head1 DESCRIPTION
+
+lcuseradd - LON-CAPA setuid script to coordinate all actions
+            with adding a user with filesystem privileges (e.g. author)
+
+=head1 README
+
+lcuseradd - LON-CAPA setuid script to coordinate all actions
+            with adding a user with filesystem privileges (e.g. author)
+
+=head1 PREREQUISITES
+
+=head1 COREQUISITES
+
+=pod OSNAMES
+
+linux
+
+=pod SCRIPT CATEGORIES
+
+LONCAPA/Administrative
+
+=cut