File:  [LON-CAPA] / loncom / Attic / lcuseradd
Revision 1.38: download - view: text, annotated - select for diffs
Fri Jul 29 17:33:18 2005 UTC (18 years, 8 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_2_0, version_2_1_X, version_2_1_99_3, version_2_1_99_2, version_2_1_99_1, version_2_1_99_0, version_2_1_3, version_2_1_2, version_2_1_1, version_2_1_0, version_2_0_X, version_2_0_99_1, version_2_0_2, version_2_0_1, version_2_0_0, version_1_99_3, HEAD
Changes to accommodate suse, where new users are put into default group rather than into new group with same name as username.  Solve this by creating group first, before adding user. Also create user's home directory if not created automatically.

#!/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)
#
#
# $Id: lcuseradd,v 1.38 2005/07/29 17:33:18 raeburn 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;
use File::Find;


# ------------------------------------------------------- Description of script
#
# This script is a setuid script that should
# 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.

# -------------- 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;

# ---------------------------------------------------- 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"),
# (15, "Error. Could not add home directory.")

# ------------------------------------------------------------- Initializations
# Security
$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.

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 (locking)
unless (&try_to_lock("/tmp/lock_lcpasswd")) {
    print "Error. Too many other simultaneous password change requests being ".
	"made.\n" unless $noprint;
    &Exit(4);
}

# ------- Error-check input, need 3 values (user name, password 1, password 2).
my @input;
if (@ARGV>=3) {
    @input=@ARGV;
} elsif (@ARGV) {
    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);
} else {
    @input=<>;
    if (@input < 3) {
	print("Error. At least three lines should be entered into standard input.\n")
	    unless $noprint;
	unlink('/tmp/lock_lcpasswd');
	&Exit(3);
    }
    foreach (@input) {chomp;}
}

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 (($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 the user if they are >not< in /etc/passwd.
#    Used to look for the ability to create a new directory for the
#    user, however that disallows authentication changes from i
#    internal->fs.. so just check the passwd file instead.
#
my $not_found = system("cut -d: -f1 /etc/passwd | grep -q \"^$safeusername\$\" ");
if (!$not_found) {
    print "Error user already exists\n" unless $noprint;
    unlink('/tmp/lock_lcpasswd');
    &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 group and user, and make www a member of the group
# -- Add group

print "adding group: $safeusername \n" unless $noprint;
my $status = system('/usr/sbin/groupadd', $safeusername);
if ($status) {
    print "Error.  Something went wrong with the addition of group ".
          "\"$safeusername\".\n" unless $noprint;
    print "Final status of groupadd = $status\n";
    unlink('/tmp/lock_lcpasswd');
    &Exit(12);
}
my $gid = getgrnam($safeusername);
                                                                                
# -- Add user

print "adding user: $safeusername \n" unless $noprint;
my $status = system('/usr/sbin/useradd','-c','LON-CAPA user','-g',$gid,$safeusername);
if ($status) {
    print "Error.  Something went wrong with the addition of user ".
	  "\"$safeusername\".\n" unless $noprint;
    system("/usr/sbin/groupdel $safeusername");
    print "Final status of useradd = $status\n";
    unlink('/tmp/lock_lcpasswd');
    &Exit(12);
}

print "Done adding user\n" unless $noprint;
# Make www a member of that user group.
my $groups=`/usr/bin/groups www` or &Exit(6);
# 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|$safeusername/} @grouplist;
my $gl=join(',',(@ugrouplist,$safeusername));
print "Putting www in user's 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);
}

# ---------------------------------------------------------------- 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;

# Check if home directory exists for user
# If not, create one.
if (!-e "/home/$safeusername") {
    if (!mkdir("/home/$safeusername",0710)) {
        print "Error. Could not add home directory for ".
          "\"$safeusername\".\n" unless $noprint;
        unlink('/tmp/lock_lcpasswd');
        &Exit(15);
    }
}

# ------------------------------ Make final modifications to the user directory
# -- Add a public_html file with a stand-in index.html file

if (-d "/home/$safeusername") {
    system('/bin/chmod','-R','0660',"/home/$safeusername");
    system('/bin/chmod','0710',"/home/$safeusername");
    mkdir "/home/$safeusername/public_html",0755;
    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;
}

#
#   In order to allow the loncapa daemons appropriate access
#   to public_html, Top level and public_html directories should
#   be owned by safeusername:safeusername as should the smaple index.html..
print "lcuseradd ownership\n" unless $noprint;
system('/bin/chown','-R',"$safeusername:$safeusername","/home/$safeusername"); # First set std ownership on everything.
&set_public_html_permissions("/home/$safeusername/public_html");
#  system('/bin/chown',"$safeusername:www","/home/$safeusername");	# Now adust top level...
#  system('/bin/chown','-R',"$safeusername:www","/home/$safeusername/public_html"); # And web dir.
# ---------------------------------------------------- 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  $pid=~ /(\D+)/;
    my $safepid = $1;
    if ($pid) {
	system('kill','-USR1',"$safepid");
    }
}
# -------------------------------------------------------- Exit script
print "lcuseradd Exiting\n" unless $noprint;
&disable_root_capability;
&Exit(0);

# ---------------------------------------------- Have setuid script run as root
sub enable_root_capability {
    if ($wwwid==$>) {
	($<,$>)=($>,0);
	($(,$))=($),0);
    } else {
	# root capability is already enabled
    }
    return $>;
}

# ----------------------------------------------- Have setuid script run as www
sub disable_root_capability {
    if ($wwwid==$<) {
	($<,$>)=($>,$<);
	($(,$))=($),$();
    } else {
	# root capability is already disabled
    }
}

# ----------------------- Make sure that another lcpasswd process isn't running
sub try_to_lock {
    my ($lockfile)=@_;
    my $currentpid;
    my $lastpid;
    # Do not manipulate lock file as root
    if ($>==0) {
	return 0;
    }
    # Try to generate lock file.
    # Wait 3 seconds.  If same process id is in
    # lock file, then assume lock file is stale, and
    # go ahead.  If process id's fluctuate, try
    # for a maximum of 10 times.
    for (0..10) {
	if (-e $lockfile) {
	    open(LOCK,"<$lockfile");
	    $currentpid=<LOCK>;
	    close LOCK;
	    if ($currentpid==$lastpid) {
		last;
	    }
	    sleep 3;
	    $lastpid=$currentpid;
	} else {
	    last;
	}
	if ($_==10) {
	    return 0;
	}
    }
    open(LOCK,">$lockfile");
    print LOCK $$;
    close LOCK;
    return 1;
}
#    Called by File::Find::find for each file examined.
#
#     Untaint the file and, if it is a directory,
#     chmod it to 02770
#
sub set_permission {
    $File::Find::name =~ /^(.*)$/;
    my $safe_name = $1;		# Untainted filename...
    
    print "$safe_name" unless $noprint;
    if(-d $safe_name) {
	print " - directory" unless $noprint;
	chmod(02770, $safe_name);
    }
    print "\n" unless $noprint;

}
#
#    Set up the correct permissions for all files in the 
#    user's public htmldir. We just do a chmod -R 0660 ... for
#    the ordinary files.  The we use File::Find
#    to pop through the directory tree changing directories only
#    to 02770:
#
sub set_public_html_permissions {
    my ($topdir) = @_;

    #   Set the top level dir permissions (I'm not sure if find 
    #   will enumerate it specifically), correctly and all
    #   files and dirs to the 'ordinary' file permissions:

    system("chmod -R 0660 $topdir");
    chmod(02770, $topdir);

    #  Now use find to locate all directories under $topdir
    #  and set their modes to 02770...
    #
    print "Find file\n " unless $noprint;
    File::Find::find({"untaint"         => 1,
		      "untaint_pattern" => qr(/^(.*)$/),
		      "untaint_skip"    => 1,
		      "no_chdir"         => 1,
		      "wanted"          => \&set_permission }, "$topdir");


}

#-------------------------- Exit...
#
#   Write the file if the error_file is defined.  Regardless
#   Exit with the status code.
#
sub Exit {
    my ($code) = @_;		# Status code.

    # TODO: Ensure the error file is owned/deletable by www:www:

    &disable_root_capability();	# We run unprivileged to write the error file.

    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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>