--- loncom/Attic/lcuseradd	2000/10/29 22:50:56	1.5
+++ loncom/Attic/lcuseradd	2004/08/05 10:56:55	1.26
@@ -1,126 +1,344 @@
 #!/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
+# YEAR=2002
+#   May 19, 2002 Ron Fox
+#      - Removed creation of the pulic_html directory.  This directory
+#        can now be added in two ways:
+#        o The user can add it themselves if they want some local web
+#          space which may or may not contain construction items.
+#        o LonCapa will add it if/when the user is granted an Author
+#          role.
 #
-# Scott Harrison
-# October 27, 2000
+# $Id: lcuseradd,v 1.26 2004/08/05 10:56:55 foxr 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).
+# These characters are:
+# 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).
 
+# ---------------------------------- 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;
-
-# These are the exit codes.
+# $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
+
+# ------------------------------------------------------------------ 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)."),
+# (3,"Error. Three lines should be entered into standard input."),
+# (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 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\"."),
+# (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;
+
+#  Error file:
+
+my $error_file;			# This is either the error file name or undef.
 
-# 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;
+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;
+print "Safe username = $safeusername \n" unless $noprint;
 
-if ($password1 ne $password2) {
-    print "Error. Password mismatch.\n";
+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);
+}
+my $pbad=0;
+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" unless $noprint;
+    unlink('/tmp/lock_lcpasswd');
+    &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)
+if (-e "/home/$safeusername") {
+    print "Error. User already exists.\n" unless $noprint;
     unlink('/tmp/lock_lcpasswd');
-    exit 7;
+    &Exit(11);
 }
 
+# -- 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);
+}
+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";
+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 5;
+    &Exit(12);
 }
-if (system('/usr/sbin/usermod','-G',$safeusername,'www')) {
-    print "Error. Could not make www a member of the group \"$safeusername\".\n";
+print "Done adding user\n" unless $noprint;
+# Make www a member of that user group.
+my $groups=`/usr/bin/groups www` or &Exit(6);
+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
+# Set password with lcpasswd (which creates smbpasswd entry).
+
+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;
+if ($?) {
+    print "abnormal Exit from close lcpasswd\n" unless $noprint;
+    &Exit(8);
 }
+($>,$<)=($wwwid,0);
+&enable_root_capability;
 
-# 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.
+# -- 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
 
+ 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>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);
+    $pid=~s/\D+//g;
+    if ($pid) {
+	system('kill','-USR1',"$pid");
+    }
+}
+# -------------------------------------------------------- Exit script
+print "lcuseradd Exiting\n" unless $noprint;
+&disable_root_capability;
+&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
@@ -128,7 +346,7 @@ sub enable_root_capability {
     return $>;
 }
 
-# ----------------------------------------------------------- have setuid script run as www
+# ----------------------------------------------- Have setuid script run as www
 sub disable_root_capability {
     if ($wwwid==$<) {
 	($<,$>)=($>,$<);
@@ -139,7 +357,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;
@@ -176,3 +394,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