| Category: | Utility |
| Author/Contact Info | jynx@dead.org |
| Description: | Where i work we have a set period of time for users to get in an account renewal form before we periodically freeze/delete accounts. Of course, anybody who turns in an account request form should also get their account renewed. The problem is that some people turn in an account request form while they already have accounts. We then renew their account and tell them to turn in a password change form if they have forgotten their password. Now that this period of getting false account creation forms is over, i generally don't get many password change requests, which is of course exactly why i have created this script now. In reality i should have taken the moment to create it long ago since an admin should never see (nonetheless type) a changed password, for his/her own security. Anyway, about the script, it is probably a *nix only script, since i use a <ctrl>-L to do a page break between the forms and i don't know how that translates to other platforms. Also, i don't provide for a third-party crypt program, which on hindsight i should have done, in case some people are super-paranoid (like a good admin should be :) The Taint checking done here isn't perfect, but there are some problems using Taint on non-CGI scripts anyway. Taint can only be used by putting -T on the command line, putting it on the shebang line will only generate an error and kill perl (thanx to footpad for reminding me of this). However, since i really don't trust myself or admins who may follow me, putting in simple checks to disallow bungling is in the machine's best interest. Whether the script is started with Taint or not, it will still do the checks and run clean, which is desirable. As for the code review, this is the (tested) first thing that came to my mind. i would be very appreciative of any comments pointing out errors or showing better WTDI. Some things have been altered to shorten this post. jynx Final Update: It turns out that pushing yp doesn't work with an empty path. So i took that out, but other than that this script is Taint compliant. |
#!/usr/local/bin/perl -w
use strict;
use File::Copy;
use Cwd;
# Set these as needed
my $DEBUG = 0;
my $SHADOW = "/etc/shadow";
my $SHADOW_TMP = "/etc/.shadow.tmp";
my $FORMS_FILE = "/tmp/pwchg.tmp";
my $GENPASS = "/usr/local/bin/genpasswd";
my $YP_DIR = ""; # define this if you use yp
my $MAKE = "/bin/make";
my $LP = "/bin/lp";
my %shadow;
# These are sanity checks (make and yp_dir checked later)
die "File not found: $SHADOW\n" unless -e $SHADOW;
die "File not found: $GENPASS\n" unless -e $GENPASS;
die "File not found: $LP\n" unless -e $LP;
die "$GENPASS is not executable.\n" unless -x $GENPASS;
die "$LP is not executable.\n" unless -x $LP;
die "Usage: $0 <username>+\n" unless @ARGV;
print STDERR map { "$_\n" } @ARGV if $DEBUG;
# If we want to read in from file, here's
# a way to scan them all in at once
my @users;
foreach my $file (@ARGV) {
if (-e $file) {
$file =~ s/\W//g; # de-Taint the filename
open USERS, $file or die "Couldn't get users: $!\n";
push @users, <USERS>;
close USERS or warn "Couldn't close file $file: $!\n";
} else {
push @users, $file;
}
}
chomp @users; # clean up spare "\n"'s from files and command line
# de-Taint usernames
foreach my $tainted (@users) {
$tainted =~ s/\W//g;
}
print STDERR map { "$_\n" } @users if $DEBUG;
# we don't want to work on the actual shadow file, in case we botch
copy($SHADOW, $SHADOW_TMP) or die "Couldn't move files: $!\n";
open SHAD, $SHADOW_TMP or die "Couldn't open shadow: $!\n";
%shadow = map {/([^:]+):(.*)/ && ($1 => "$1:$2")} <SHAD>;
close SHAD or warn "Couldn't close shadow: $!\n";
# The hard work of the script...
open FORMS, ">$FORMS_FILE" or die "Couldn't open forms file: $!\n";
my ($new_pass, $new_crypt, $salt);
foreach my $user (@users) {
if ($shadow{$user}) {
chomp( $new_pass =`$GENPASS` );
$new_pass =~//;
$salt = gen_salt();
$new_crypt = crypt($new_pass, $salt);
$shadow{$user} =~ s/([^:]+):[^:]+:(.*)/$1:$new_crypt:$2/;
print FORMS pass_info($user,$new_pass);
print FORMS "^L" unless $user eq $users[$#users];
} else {
print FORMS no_account($user);
print FORMS "^L" unless $user eq $users[$#users];
}
}
close FORMS or die "Couldn't close $FORMS_FILE: $!\n";
# While we're here, why not sort the shadow file?
open SHAD, ">$SHADOW_TMP" or die "Couldn't open shadow: $!\n";
print SHAD map {$_->[1],"\n"}
sort{$a->[0] cmp $b->[0]}
map {[$_,$shadow{$_}]} keys %shadow;
close SHAD or die "Couldn't close shadow: $!\n";
copy($SHADOW_TMP, $SHADOW) or warn "Couldn't move tmp to shadow: $!\n"
+;
# The newly thought out YP section (now tested and working)
{
last unless $YP_DIR;
warn "Failed to find/run $MAKE for yp.\n",last unless -e $MAKE && -x
+ _;
my $cur_dir = cwd;
if (chdir($YP_DIR)) {
system("$MAKE");
warn $@ if $@;
chdir $cur_dir or warn "Couldn't go to $cur_dir: $!\n";
} else {
warn "Couldn't push yp: $!\n";
}
}
# print the forms
my $status = `$LP $FORMS_FILE`;
print "Printing ", scalar @users, " forms: $status\n";
END { unlink($FORMS_FILE,$SHADOW_TMP) unless $DEBUG }
sub gen_salt {
join '', ('.', '/', 0..9, 'a'..'z', 'A'..'Z')[rand(64),rand(64)]
}
sub pass_info {
my ($user,$pass) = @_;
my $date = scalar localtime;
return <<INFO;
DATE: $date
TO: $user
You have a new passwd for this system:
Login name: $user
New Passwd: $pass
INFO
}
sub no_account {
my $user = shift;
my $date = scalar localtime;
return <<NO;
DATE: $date
You have no account on this system, pick up an
Account Creation Form.
Login name: $user
NO
}
__END__
|
|
|
|---|