--- loncom/lcpasswd	2000/10/27 19:50:24	1.1
+++ loncom/lcpasswd	2001/11/15 18:34:57	1.13
@@ -1,113 +1,224 @@
 #!/usr/bin/perl
 
-use strict;
+# The Learning Online Network with CAPA
+#
+# lcpasswd - LON-CAPA setuid script to synchronously change all
+#            filesystem-related passwords (samba, unix, etc)
+#
+# YEAR=2000
+# 10/27,10/28,10/29,10/30 Scott Harrison
+#
+# YEAR=2001
+# 10/22,10/23,11/13,11/15 Scott Harrison
+#
+# $Id: lcpasswd,v 1.13 2001/11/15 18:34:57 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                                                             ##
+##                                                                           ##
+###############################################################################
 
-# Scott Harrison
-# October 27, 2000
+use strict;
 
+# ------------------------------------------------------- Description of script
+#
 # This script is a setuid script that should
-# be run by user 'www'.
+# 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
 # Third line is NEW PASSWORD
+#
+# 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 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'}=""; # 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
 
-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");
-    exit 0;
-}
-if (@ARGV) {
-    print("Error. This program does not accept command-line arguments.\n");
-    exit 0;
-}
+# Do not print error messages
+my $noprint=1;
 
-# Gather input from standard input.  Should only be 3 lines.
-my @input=<>;
-if (@input!=3) {
-    print("Error. Three lines need to be entered into standard input.\n");
-    exit 0;
+# ----------------------------- 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;
 }
 
-# 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";
-    exit 0;
-}
+# ----------------------------------- Start running script with www permissions
+&disable_root_capability;
 
-my ($username,$oldpwd,$newpwd)=map {chop; $_} @input;
+# --------------------------- 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;
+}
 
-# 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;}
+# ------- 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;
+}
+map {chomp} @input;
+
+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;
+map {if (($_<32)&&($_>126)){$pbad=1;}} (split(//,$password1));
+map {if (($_<32)&&($_>126)){$pbad=1;}} (split(//,$password2));
+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)) {
-    print "Error. User $username does not exist.\n";
-    exit 0;
-}
-
-# Verify password entry
-if (crypt($oldpwd,$useroldcryptpwd) ne $useroldcryptpwd) {
-    print "Error. Invalid entry of current password.\n";
-    exit 0;
-}
-
-# Construct new password entry
-my $newcryptpwd=crypt($newpwd,$newpwd);
-$U[1]=$newcryptpwd;
-my $userline=join(":",@U);
-print $newcryptpwd;
-print $userline;
-#my $rootid=&enable_root_capability;
-#if ($rootid!=0) {
-#    print "Error.  Root was not successfully enabled.\n";
-#    exit 0;
-#}
-# open SAMBAPASSWORDFILE, ">/etc/smbpasswd";
-	($<,$>)=($>,$<);
-	($(,$))=($),$();
-open PASSWORDFILE, "/tmp/passwd2" 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";}
+unless(getpwnam($safeusername)) {
+    print "Error. User $username does not exist.\n" unless $noprint;
+    unlink('/tmp/lock_lcpasswd');
+    exit 5;
+}
+
+&enable_root_capability;
+($>,$<)=(0,0);
+open OUT,"|pwchange $safeusername";
+print OUT $password1;
+print OUT "\n";
+close OUT;
+($>,$<)=(0,500);
+
+if ($?) {
+    exit 8;
+}
+my $userid=getpwnam($safeusername);
+
+if (-e '/usr/bin/smbpasswd') {
+
+    ($>,$<)=(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:XXXXXXXXXXXXXX'.
+			    'XXXXXXXXXXXXXXXXXX','','/home/'.$safeusername,
+			    '/bin/bash')) . "\n";
+	close OUT;
+    }
+
+    open(OUT,"|/usr/bin/smbpasswd -s $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
 }
-close PASSWORDFILE;
-# close SAMBAPASSWORDFILE;
+
 &disable_root_capability;
-unlink("/tmp/lock_lcpasswd");
+unlink('/tmp/lock_lcpasswd');
+exit 0;
 
+# ---------------------------------------------- have setuid script run as root
 sub enable_root_capability {
-    if ($wwwid==$<) {
+    if ($wwwid==$>) {
 	($<,$>)=($>,$<);
 	($(,$))=($),$();
     }
     else {
 	# root capability is already enabled
     }
-    return $<;
+    return $>;
 }
 
+# ----------------------------------------------- have setuid script run as www
 sub disable_root_capability {
-    if ($wwwid==$>) {
+    if ($wwwid==$<) {
 	($<,$>)=($>,$<);
 	($(,$))=($),$();
     }
@@ -116,10 +227,20 @@ sub disable_root_capability {
     }
 }
 
+# ----------------------- 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");
@@ -143,3 +264,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