--- loncom/lcpasswd	2000/10/27 23:32:24	1.4
+++ loncom/lcpasswd	2003/02/03 18:03:52	1.19
@@ -1,166 +1,204 @@
 #!/usr/bin/perl
+# The Learning Online Network with CAPA
 #
-# lcpasswd
+# lcpasswd - LON-CAPA setuid script to synchronously change all
+#            filesystem-related passwords (samba, unix, etc)
 #
-# Scott Harrison
-# October 27, 2000
+# YEAR=2002
+# 02/19 Matthew Hall
+#
+# $Id: lcpasswd,v 1.19 2003/02/03 18:03:52 harris41 Exp $
+###
+
+###############################################################################
+##                                                                           ##
+## ORGANIZATION OF THIS PERL SCRIPT                                          ##
+##                                                                           ##
+## 1. Description of script                                                  ##
+## 2. Invoking script (standard input only)                                  ##
+## 3. Example usage inside another piece of code                             ##
+## 4. Description of functions                                               ##
+## 5. Exit codes                                                             ##
+##                                                                           ##
+###############################################################################
 
 use strict;
 
+# ------------------------------------------------------- Description of script
+#
 # This script is a setuid script that should
 # be run by user 'www'.  This script allows
 # for synchronous entry of passwords into
 # both the /etc/passwd and the /etc/smbpasswd
 # files.
+#
+# This script works under the same process control mechanism
+# as lcuseradd and lcpasswd, to make sure that only one of these
+# processes is running at any one time on the system.
 
+# --------------------------------------- Invoking script (standard input only)
+#
 # Standard input usage
 # First line is USERNAME
-# Second line is CURRENT PASSWORD
+# Second line is NEW PASSWORD
 # Third line is NEW PASSWORD
-
-# Command-line arguments
-# Yes, but be very careful here (don't pass shell commands)
-# and this is only supported to allow perl-system calls.
-
-# Usage within code
-# Note: NEVER run as system("NAME OLDPWD NEWPWD")
 #
-# $exitcode=system("NAME","OLDPWD","NEWPWD")/256;
-# print "uh-oh" if $exitcode;
+# 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).
+
+# ---------------------------------------------------- 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 does not accept command-line arguments."),
-#   (3,"Error. Three lines need to be entered into standard input.\n"),
-#   (4,"Error. Too many other simultaneous password change requests being made.\n"),
-#   (5,"Error. User $username does not exist.\n"),
-#   (6,"Error. Invalid entry of current password.\n"),
-#   (7,"Error.  Root was not successfully enabled.\n") )
+#   (1,"User ID mismatch.  This program must be run as user 'www'"),
+#   (2,"Error. This program needs 3 command-line arguments (username, old ".
+#       password, new password)."),
+#   (3,"Error. Three lines need to 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. Invalid entry of current password."),
+#   (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.") )
 
+# ------------------------------------------------------------- Initializations
 # Security
-$ENV{'PATH'}="/bin:/usr/bin"; # Nullify path information except for what smbpasswd needs
-$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 if there are command-line arguments
-my $noprint=0;
-if (@ARGV) {
-    $noprint=1;
-}
-
-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;
+# Do not print error messages
+my $noprint=1;
+
+print "In lcpasswd" 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;
 
-# Gather input.  Should only be 3 values.
-my @input;
-if (@ARGV==3) {
-    @input=@ARGV;
-}
-elsif (@ARGV) {
-    print("Error. This program needs 3 command-line arguments (username, old password, new password).\n") unless $noprint;
-    exit 2;
-}
-else {
-    @input=<>;
-    if (@input!=3) {
-	print("Error. Three lines need to be entered into standard input.\n") unless $noprint;
-	exit 3;
-    }
-    map {chop} @input;
-}
-# Handle case of another lcpasswd process
-unless (&try_to_lock("/tmp/lock_lcpasswd")) {
-    print "Error. Too many other simultaneous password change requests being made.\n" unless $noprint;
+# --------------------------- 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;
 }
 
-my ($username,$oldpwd,$newpwd)=@input;
+# ------- Error-check input, need 3 values (user name, password 1, password 2).
+my @input;
+@input=<>;
+if (@input!=3) {
+    print("Error. Three lines need to be entered into standard input.\n")
+	unless $noprint;
+    unlink('/tmp/lock_lcpasswd');
+    exit 3;
+}
+foreach (@input) {chomp;}
 
-# Grab the line corresponding to username
-my ($userid,$useroldcryptpwd);
-my @F; my @U;
-for my $l (@lines) {
-    @F=split(/\:/,$l);
-    if ($F[0] eq $username) {($userid,$useroldcryptpwd)=($F[2],$F[1]); @U=@F;}
+my ($username,$password1,$password2)=@input;
+$username=~/^(\w+)$/;
+my $safeusername=$1;
+if (($username ne $safeusername) or ($safeusername!~/^[A-Za-z]/)) {
+    print "Error. The user name specified has invalid characters.\n";
+    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";
+    unlink('/tmp/lock_lcpasswd');
+    exit 10;
+}
+
+# -- 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;
 }
 
 # Verify existence of user
-if (!defined($userid)) {
+unless(getpwnam($safeusername)) {
     print "Error. User $username does not exist.\n" unless $noprint;
-    unlink("/tmp/lock_lcpasswd");
+    unlink('/tmp/lock_lcpasswd');
     exit 5;
 }
+&enable_root_capability;
+($>,$<)=(0,0);
+
+print "Now $> , $< , -invoking pwchange with $safeusername $password1"
+    unless $noprint;
+open OUT,"|pwchange $safeusername";
+print OUT $password1;
+print OUT "\n";
+close OUT;
+($>,$<)=(0,500);
 
-# Verify password entry
-if (crypt($oldpwd,$useroldcryptpwd) ne $useroldcryptpwd) {
-    print "Error. Invalid entry of current password.\n" unless $noprint;
-    unlink("/tmp/lock_lcpasswd");
-    exit 6;
-}
-
-# Construct new password entry (random salt)
-my $newcryptpwd=crypt($newpwd,(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 die("Cannot open /etc/passwd!");
-for my $l (@lines) {
-    @F=split(/\:/,$l);
-    if ($F[0] eq $username) {print PASSWORDFILE "$userline\n";}
-    else {print PASSWORDFILE "$l\n";}
+print "pwchange done, back to uid 500" unless $noprint;
+
+if ($?) {
+    exit 8;
 }
-close PASSWORDFILE;
-$username=~/^(\w+)$/;
-my $safeusername=$1;
-($>,$<)=(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";
+my $userid=getpwnam($safeusername);
+
+if (-e '/usr/bin/smbpasswd') {
+
+    ($>,$<)=(0,0); # fool smbpasswd here to think this is not a setuid
+                   # environment
+
+#   If the -a swithc is put on the smbpasswd
+# command line, either a new entry will be created or the old one
+# will be used. 
+# Therefore the old strategy of looking for and adding a dummy entry is 
+# not needed... Finally, the smbpasswd file is in /etc/samba not 
+# /etc/smbpasswd as older versions of the script implied.
+
+    print "Running smbpasswd" unless $noprint;
+    open(OUT,"|/usr/bin/smbpasswd -s -a $safeusername>/dev/null") or
+	die('cannot run smbpasswd');
+    print OUT $password2; print OUT "\n";
+    print OUT $password2; print OUT "\n";
     close OUT;
+    $<=$wwwid; # unfool the program
+    print "smbpasswd done" unless $noprint;
 }
-open(OUT,"|/usr/bin/smbpasswd -s $safeusername>/dev/null");
-print OUT $newpwd; print OUT "\n";
-print OUT $newpwd; print OUT "\n";
-close OUT;
-$<=$wwwid; # unfool the program
+
 &disable_root_capability;
-unlink("/tmp/lock_lcpasswd");
+unlink('/tmp/lock_lcpasswd');
 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
@@ -168,7 +206,7 @@ sub enable_root_capability {
     return $>;
 }
 
-# ----------------------------------------------------------- have setuid script run as www
+# ----------------------------------------------- have setuid script run as www
 sub disable_root_capability {
     if ($wwwid==$<) {
 	($<,$>)=($>,$<);
@@ -179,11 +217,20 @@ 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;
     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");
@@ -207,3 +254,32 @@ sub try_to_lock {
     close LOCK;
     return 1;
 }
+
+=head1 NAME
+
+lcpasswd - LON-CAPA setuid script to synchronously change all
+           filesystem-related passwords (samba, unix, etc)
+
+=head1 DESCRIPTION
+
+LON-CAPA setuid script to synchronously change all
+filesystem-related passwords (samba, unix, etc)
+
+=head1 README
+
+LON-CAPA setuid script to synchronously change all
+filesystem-related passwords (samba, unix, etc)
+
+=head1 PREREQUISITES
+
+=head1 COREQUISITES
+
+=pod OSNAMES
+
+linux
+
+=pod SCRIPT CATEGORIES
+
+LONCAPA/Administrative
+
+=cut