Cover V01, I01
Article
Listing 1
Listing 2
Listing 3
Sidebar 1
Sidebar 2

may92.tar


Listing 2: pwd.pl

/bin/passwd model program

#! suidperl
eval '(exit $?0)' && eval 'exec suidperl $0
${1+"$@"}'
& eval 'exec suidperl $0 $argv:q'
if 0;

#
# Demonstrate how to use PERL to test for the conditions mentioned in the
# article
#
#
# set some default conditionals
#
$LocalWords = "/etc/badwords";          # location of badword list
$pwd_length = "6";                      # minimum password length
$PasswordFile = "/etc/passwd";          # where is the passwd file?
$TempFile = "/tmp/ptmp";                # temporary copy of passwd
$Dictionary = "/usr/dict/words";        # Dictionary of words
$ENV{'PATH'} = "/bin:/usr/bin:/etc";

#
# setup the signal handler, so that things are done right when we abort
#
$SIG{'INT'} = 'DoExit';
$SIG{'TERM'} = 'DoExit';

#
# ensure security things are OK
#
if ( $> != 0 )
{
printf "error: problem running SUID\n";
&DoExit(1);
}
#
# Check to see if things are set up right
#
die "passwd file locked! Try again.\n" if ( -f "$TempFile" );
#
# OK, create the tempfile
#
open( TEMP, ">$TempFile" ) || die "can't open temp file";
#
# Insure that we are dealing with the user at the keyboard by closing
# STDIN and STDOUT.  Then re-open the /dev/tty
#
close( STDIN );
close( STDOUT );
open( TERM, "+>/dev/tty" );
#
# set the TERM descriptor to be the one which is put in place
#
select( TERM );

#
# get the passwd file entry for this account.  $< is the numerical
# representation of our REAL UID
#
( $me, $mypasswd, $uid, $gid, $pwage,
$comment, $gcos, $dir, $shell ) = getpwuid($<);
#
# $pw_salt is the first two characters of the SALT for the existing
# password
#
$pw_salt = substr( $mypasswd, 0, 2 );
#
# NOTE : presently, we are ignoring any reference to password aging
#
printf "Changing password for : $me\n";
#
# collect the old passwd
#
printf "Old password: ";
#
# the /bin/stty -echo turns of terminal echo
#
system "/bin/stty -echo";
$old_passwd = <TERM>;
#
# the /bin/stty echo turns on terminal echo
#
system( "/bin/stty echo" );
chop( $old_passwd );
printf "\n";

#
# check to see if the passwords are the same
#
if ( crypt( $old_passwd, $pw_salt ) eq $mypasswd )
{
#
# Yup, so go and work on getting a new password
#
&DoNewPasswd;
}
else
{
#
# Nope, the password is wrong, so tell the user and exit.
#
printf "Sorry.\n";
&DoExit(1);
}

#
# the DoNewPasswd function will process the local configuration file and
# work on getting a new password
#
sub DoNewPasswd
{
#
# load our site specific password configuration file
#
require "passwd.cfg" if ( -f passwd.cfg);
#
# now get the password and process it
#
&GetNewPassword;
#
# create the new passwd file
#
&BuildNewPasswd;
#
# reset terminal and exit
#
system( "/bin/stty echo" );
&DoExit(0);
}

sub GetNewPassword
{
printf "You will now be prompted to enter a new password.  The following
rules must be adhered to when entering this :

it must be at least $pwd_length characters long
it must contain at least one numeric or special character
it cannot contain your username, or words like UNIX, XENIX, guru
\n";

#
# We will loop around only three times
#
$IsaidIt = 0;
while ( $IsaidIt < 3 )
{
#
# we will give the user THREE chances to change it.
#
$new_passwd = &CollectPasswd;

if ( &GoodPW( $new_passwd ) )
{
return(0);
}
else
{
#
# Ooops! it didn't meet at least one of the criteria
#
$IsaidIt++;
}
}
printf "Too many unsuccessful attempts - change aborted.\n";
return(0);
}

#
# Actually get the password from the user
#
sub CollectPasswd
{
#
# set the collectible spaces to some junk variables for the loop
#
$pass0 = "x";
$pass1 = "y";
$try = 0;

#
# again - we will loop around three times - this provides a second
# chance for the user
#
while ( $try < 3 )
{
#
# print the prompt, and then turn off the terminal echo
# then collect the first attempt
#
printf "New password: ";
system( "/bin/stty -echo" );
$pass0 = <TERM>;
system( "/bin/stty echo" );
printf "\n";
chop( $pass0 );
#
# check to see if the user is trying to use the same password
#
if ( $pass0 eq $old_passwd )
{
$try++;
printf "You cannot re-use the same password.\n";
next;
}
#
# check the length here
#
if ( length( $pass0 ) < $pwd_length )
{
$try++;
printf "Your new password must be at least %s characters\n",
$pwd_length;
next;
}
#
# check for a number
#
if ( ! &CheckDigits($pass0) )
{
$try++;
printf "Your new password must contain at least one numeric or special character.\n";
next;
}
#
# print the prompt, and then turn off the terminal echo
# then collect the first attempt
#
printf "Re-enter New password: ";
system( "/bin/stty -echo" );
$pass1 = <TERM>;
system( "/bin/stty echo" );
printf "\n";
chop( $pass1 );
#
# If they are the some, then return the collected password to the
# calling program
#
if ( $pass0 eq $pass1 )
{
return( $pass0 );
}
else
{
#
# then tell the user that they don't match and loop again
#
$try++;
printf "They don't match.  Try again.\n";
}
}
#
# we will reach this if we have had too many failures
#
printf "Too many failures - aborted!\n";
&DoExit(1);
}

#
# this routine will verify that there is a digit and/or a non-printable
# character in the argument provided
#
sub CheckDigits
{
local ( $pass ) = @_;

if ( $pass0 =~ /[0-9]/ )
{
#
# there is at least one digit
#
return(1);
}
else
{
#
# check to see if there are any non-printable characters in here
#
for ( $x = 0; $x < length($pass); $x++ )
{
#
# split off a character
#
$y = substr( $pass, $x, 1 );
#
# get the ASCII value - if it is less than 32 (space), then it
# is a control character
#
if ( ord($y) < 32 )
{
return(1);
}
}
return(0);
}
}

#
# this routine will evaluate the qaulity of the good password
#
sub GoodPW
{
local( $pass ) = @_;

printf "Checking your password for obviousness.\n";
#
# now we are going to do some checking
#
#
# process the password file
#
open( pwd, "/etc/passwd" ) || die "can't open /etc/passwd\n";
while (<pwd>)
{
( $username, $passwd, $uid, $gid,
$comment, $dir, $shell ) = split(/:/);
#
# Check 1 - is the new password one of the existing user names?
#
if ( $pass =~ /$username/i )
{
# if the value of $$pass exists in any part of the vlogin name,
# the junk tha password
printf "Can't use a valid login name as part of the password.\n";
return(0);
}
}

close( pwd );
#
# Now do some further analysis - read through the list of poor words
# and see if we have a match
#
open( pwd, "$LocalWords" ) if ( defined( $LocalWords ) );
while (<pwd>)
{
( $word, @message ) = split(/:/);
#
# if we have a match, print the message which was stored with this
# word
#
if ( $word =~ /^$pass*/i )
{
printf "bad password : %s\n", @message;
return(0);
}
if ( $word =~ /.*$pass$/i )
{
printf "bad password : %s\n", @message;
return(0);
}
}

close( pwd );
#
# Now do some further analysis - look through the dictionary
#
open( pwd, "$Dictionary" ) if ( defined($Dictionary) );
while (<pwd>)
{
chop;
if ( $_ =~ /$pass/i )
{
printf "bad password : Its in the dictionary\n";
return(0);
}
}

return(1);
}

sub BuildNewPasswd
{
seek ( TEMP, 0, 0 );
open( PWD, "$PasswordFile" );

while (<PWD>)
{
( $username, $passwd, $uid, $gid, $comment, $dir, $shell ) = split(/:/);
if ( $username ne $me )
{
printf TEMP "$_";
}
else
{
printf "UPDATING $pwage \n";
$new_passwd = crypt( $new_passwd, $pw_salt );
if ( $pwage eq "" )
{
printf "%s:%s:%s:%s:%s:%s:%s:%s",
$username,$new_passwd, $uid,
$gid, $comment, $dir, $shell;
printf TEMP "%s:%s:%s:%s:%s:%s:%s:%s",
$username,$new_passwd, $uid,
$gid, $comment, $dir, $shell;
}
else
{
printf TEMP "%s:%s,%s:%s:%s:%s:%s:%s:%s",
$username,$new_passwd, $pwage, $uid,
$gid, $comment, $dir, $shell;
}
}
}
printf "Password for $me updated.\n";
return(0);
}
#
# this routine is called by the interrupt handlers and at various places in
# order to remove the temporary file and exit
#
sub DoExit
{
local( $value ) = @_;
#
# make sure that terminal echo is enabled
#
system('/bin/stty echo');
#
# remove the ptmp file
#
close( TEMP );
#
# rename the existing passwd file
#
rename( "/etc/passwd", "/etc/opasswd" );
link( "$TempFile", "/etc/passwd" );
#unlink( "$Tempfile" );
#
# exit
#
exit($value);
}