--- loncom/lcpasswd	2000/10/29 22:38:21	1.7
+++ loncom/lcpasswd	2005/03/26 20:40:18	1.20
@@ -1,176 +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
-# SH: October 27, 2000
-# SH: October 28, 2000
+# YEAR=2002
+# 02/19 Matthew Hall
+#
+# $Id: lcpasswd,v 1.20 2005/03/26 20:40:18 albertel 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("/home/httpd/perl/lcpasswd NAME OLDPWD NEWPWD")
 #
-# $exitcode=system("/home/httpd/perl/lcpasswd","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."),
+#   (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."),
+#   (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 open /etc/passwd.") )
+#   (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;
-}
-
-# 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;
+# 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;
 
-# 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;
+    print "Error. Too many other simultaneous password change requests being ".
+	"made.\n" unless $noprint;
     exit 4;
 }
 
-# Gather input.  Should only be 3 values.
+# ------- Error-check input, need 3 values (user name, password 1, password 2).
 my @input;
-if (@ARGV==3) {
-    @input=@ARGV;
+@input=<>;
+if (@input!=3) {
+    print("Error. Three lines need to be entered into standard input.\n")
+	unless $noprint;
+    unlink('/tmp/lock_lcpasswd');
+    exit 3;
 }
-elsif (@ARGV) {
-    print("Error. This program needs 3 command-line arguments (username, old password, new password).\n") unless $noprint;
+foreach (@input) {chomp;}
+
+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 2;
+    exit 9;
 }
-else {
-    @input=<>;
-    if (@input!=3) {
-	print("Error. Three lines need to be entered into standard input.\n") unless $noprint;
-	unlink('/tmp/lock_lcpasswd');
-	exit 3;
-    }
-    map {chop} @input;
-}
-
-my ($username,$oldpwd,$newpwd)=@input;
-
-# 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 $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');
     exit 5;
 }
+&enable_root_capability;
+($>,$<)=(0,0);
 
-# 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;
-}
+print "Now $> , $< , -invoking pwchange with $safeusername $password1"
+    unless $noprint;
+open OUT,"|pwchange $safeusername";
+print OUT $password1;
+print OUT "\n";
+close OUT;
+($>,$<)=(0,$wwwid);
 
-# 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 (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";}
+print "pwchange done, back to uid $wwwid" 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');
 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
@@ -178,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==$<) {
 	($<,$>)=($>,$<);
@@ -189,7 +217,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;
@@ -226,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