--- loncom/lcpasswd	2000/10/27 22:13:40	1.3
+++ loncom/lcpasswd	2000/10/30 03:08:28	1.10
@@ -3,23 +3,74 @@
 # lcpasswd
 #
 # Scott Harrison
-# October 27, 2000
+# SH: October 27, 2000
+# SH: October 28, 2000
 
 use strict;
 
 # 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.
 
 # 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).
+
+# 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;
+
+# 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 open /etc/passwd."),
+#   (9,"Error. The user name specified has invalid characters."),
+#   (10,"Error. A password entry had an invalid character.") )
+
 # 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'; # Nullify path information except for what smbpasswd needs
+$ENV{'BASH_ENV'}=''; # Nullify shell environment information.
+
+# Do not print error messages if there are command-line arguments
+my $noprint=0;
+if (@ARGV) {
+    $noprint=1;
+}
 
-open (IN, "</etc/passwd");
+# 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;
@@ -29,29 +80,53 @@ for my $l (@lines) {
     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;
+    print("User ID mismatch.  This program must be run as user 'www'\n") unless $noprint;
+    exit 1;
 }
 &disable_root_capability;
-if (@ARGV) {
-    print("Error. This program does not accept command-line arguments.\n");
-    exit 0;
-}
-
-# 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;
-}
 
 # 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;
+unless (&try_to_lock('/tmp/lock_lcpasswd')) {
+    print "Error. Too many other simultaneous password change requests being made.\n" unless $noprint;
+    exit 4;
+}
+
+# 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;
+    unlink('/tmp/lock_lcpasswd');
+    exit 2;
+}
+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)=map {chop; $_} @input;
+my ($username,$oldpwd,$newpwd)=@input;
+$username=~/^(\w+)$/;
+my $safeusername=$1;
+if ($username ne $safeusername) {
+    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(//,$oldpwd));
+map {if (($_<32)&&($_>126)){$pbad=1;}} (split(//,$newpwd));
+if ($pbad) {
+    print "Error. A password entry had an invalid character.\n";
+    unlink('/tmp/lock_lcpasswd');
+    exit 10;
+}
 
 # Grab the line corresponding to username
 my ($userid,$useroldcryptpwd);
@@ -63,40 +138,42 @@ for my $l (@lines) {
 
 # Verify existence of user
 if (!defined($userid)) {
-    print "Error. User $username does not exist.\n";
-    exit 0;
+    print "Error. User $username does not exist.\n" unless $noprint;
+    unlink('/tmp/lock_lcpasswd');
+    exit 5;
 }
 
 # Verify password entry
 if (crypt($oldpwd,$useroldcryptpwd) ne $useroldcryptpwd) {
-    print "Error. Invalid entry of current password.\n";
-    exit 0;
+    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 $userline=join(':',@U);
 my $rootid=&enable_root_capability;
 if ($rootid!=0) {
-    print "Error.  Root was not successfully enabled.\n";
-    exit 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!");
+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";}
 }
 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;
+unless (-e '/etc/smbpasswd') {
+    open (OUT,'>/etc/smbpasswd'); close OUT;
 }
 my $smbexist=0;
-open (IN, "</etc/smbpasswd");
+open (IN, '</etc/smbpasswd');
 my @lines=<IN>;
 close IN;
 for my $l (@lines) {
@@ -105,8 +182,8 @@ for my $l (@lines) {
     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";
+    open(OUT,'>>/etc/smbpasswd');
+    print OUT join(':',($safeusername,$userid,'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX','','/home/'.$safeusername,'/bin/bash')) . "\n";
     close OUT;
 }
 open(OUT,"|/usr/bin/smbpasswd -s $safeusername>/dev/null");
@@ -115,8 +192,10 @@ 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
 sub enable_root_capability {
     if ($wwwid==$>) {
 	($<,$>)=($>,$<);
@@ -128,6 +207,7 @@ sub enable_root_capability {
     return $>;
 }
 
+# ----------------------------------------------------------- have setuid script run as www
 sub disable_root_capability {
     if ($wwwid==$<) {
 	($<,$>)=($>,$<);
@@ -138,10 +218,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");