Thursday, February 7, 2008

Shell Script To Disable and Delete User Accounts - Follow Up

Hey there,

This is a long overdue follow up to an older post where we began walking through the process of developing a decent and useful shell script, from scratch, to identify user accounts to disable or terminate based on the amount of time passed since they last logged in. Our goal was to make it simple to use and workable on RedHat Linux and Solaris Unix.

The rules are still the same: 45 days without a login equals account password locking and 90 days without a login equals account deletion. All actions (the password locking and account deletion) are still being kept manual, since the script isn't finished yet. You could argue that, no matter how good the script gets, that kind of stuff should always be kept manual, just in case. Don't want to delete the wrong person's account ;)

In this update, we've integrated another cross-check, utilizing Perl to get relevant information from the system wtmpx file (Referenced in greater detail in this post on taking a look inside wtmpx). We'll need to use this to provide a comparison against the modification time checks we're already doing on the users' home directories.

Important Note: Since this script is for both Linux and Unix, I made the location of the wtmpx file a variable, since it resides in /var/adm on Solaris and /var/log on RedHat Linux. The variable (comment out the one you need to) $wtmpx_file_loc has been added to address that issue. There are still other issues with Linux's implementation of wtmp that we will look at tomorrow (So that this script will run correctly on that OS. Please, no complaints until then ;)

Neither check is guaranteed to give a correct result, but utilizing both of them will quickly point out accounts flagged for deletion or lockdown that actually shouldn't be! In certain circumstances, both checks will fail to give entirely definitive results (for instance, if a user's home directory is over 90 days old and he/she doesn't have any entries in wtmpx, it's possible that we'd be deleting or locking the account in error, but much less likely).

Note that this is still a work in progress since, right now, we've got it printing out information to the screen for manual verification. In our next update, we'll take the wtmpx output (crucial to use Perl for this because we need the "year" and commands like "last" and "logins" don't provide that information) and convert it, so that we can have the "time-since-last-login" it provides to directly cross-reference against the filesystem checks inside the script; thus removing that manual part of the verification.

We've also made the user notifications a bit more warm and cuddly. Always in the best interest of everyone to be courteous when we can :)


Cheers,


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/usr/bin/perl

#
# accountminder.pl
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

$hostname=`hostname`;
$wtmpx_counter = 500;
$wtmpx_total = 0;
$wtmpx_file_loc = "/var/adm/wtmpx"; # For Solaris
# $wtmpx_file_loc = "/var/log/wtmp"; # For RedHat
print "PROCESSING WTMPX FILE FOR CROSS-CHECKING: ";
$| = 1;
$template = "A32 A4 A32 l s s2 x2 l2 l x20 s A257 x";
$recordsize = length(pack($template,( )));
open(WTMP,$wtmpx_file_loc) or die "Unable to open wtmpx:$!\n";
while (read(WTMP,$record,$recordsize)) {
if ( $wtmpx_counter == 1 ) {
print ".";
$wtmpx_counter = 500;
$wtmpx_total++;
}
($ut_user,$ut_id,$ut_line,$ut_pid,$ut_type,$ut_e_termination,
$ut_e_exit,$tv_sec,$tv_usec,$ut_session,$ut_syslen,$ut_host)=
unpack($template,$record);
push (@wtmpx_uv, "$ut_user ".scalar localtime($tv_sec));
$wtmpx_counter--;
$wtmpx_total++;
}
close(WTMP);
print ".\n";
print "PROCESSED $wtmpx_total ENTRIES\n";

$today=time();

@no_shells=`egrep -i 'nologin|false' /etc/passwd|awk -F":" '{print \$1}'`;
@in_admin=`grep -w 99999 /etc/passwd|awk -F":" '{print \$1}'`;
@user_names=`awk -F":" '{print \$1}' /etc/passwd`;
open(LOCKEDTEXT, "<locked.txt");
@locked_text = <LOCKEDTEXT>;
close(LOCKEDTEST);
print "\nRemoving LOCKED ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@locked_text ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n";
print "\nRemoving NO_SHELL ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@no_shells ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n";
print "\nRemoving ADMINISTRATIVE ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@in_admin ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n\n";
@total = @both = @unique_names = ();
%count = ();
foreach $possible_dupe (@user_names, @tmp_user_names) {
$count{$possible_dupe}++
}
foreach $possible_dupe (keys %count) {
push @total, $possible_dupe;
push @{ $count{$possible_dupe} > 1 ? \@both : \@unique_names }, $possible_dupe;
}
@user_names = @unique_names;
foreach $diruser (@user_names) {
$filer = `grep $diruser /etc/passwd|awk -F":" '{print \$6}'`;
push(@user_dirs,$filer);
}
print "FIRST RUN - Checking Home Directories...\n\n";
foreach $history_file (@user_dirs) {
chomp($history_file);
chomp($user_name = $user_names[$counter]);
print "${user_name}: ";
$counter++;
if ( -d "$history_file" ) {
@file_dates=stat("$history_file");
$file_date=$file_dates[9];
} else {
$file_date=0;
}
$difference=$today - $file_date;
if ( $difference >= 3888000 && $difference < 7776000 ) {
# print "45 days old or older, but under 90\n";
# print "passwd -l would be executed\n";
$| = 1;
print "Lock Em Up!\n";
print "Verifying Last Login: ";
$wtmpx_user = grep(/$user_name/, @wtmpx_uv);
if ( $wtmpx_user == 0 ) {
print "No Entries in $wtmpx_file_loc\n";
} else {
@wtmpx_user = grep(/$user_name/, @wtmpx_uv);
$verified = $wtmpx_user[$#wtmpx_user];
print "$verified\n";
}
push(@broken, $user_name);
open(SENDMAIL, "|/usr/lib/sendmail -t");
print SENDMAIL "From: System Administration\n";
print SENDMAIL "Reply-To: sec_account\@xyz.com\n";
print SENDMAIL "To: $user_name@\xyz.com\n";
print SENDMAIL "Subject: $user_name Account LockDown Notice\n";
print SENDMAIL "$user_name,\n";
print SENDMAIL "\n";
print SENDMAIL "\tWe found that you haven't logged into your account on unix server $hostname within the last 45 days.\n";
print SENDMAIL "\n";
print SENDMAIL "\tYour account has been locked. If you still require this access, please have your unix password reset.\n";
print SENDMAIL "\n";
print SENDMAIL "\tIf your account remains dormant for 90 days, your account will be deleted.\n";
print SENDMAIL "\n";
print SENDMAIL "Thanks,\n";
print SENDMAIL "\tSystem Administration\n";
close(SENDMAIL);
} elsif ( $difference >= 7776000 ) {
push(@buckwheats, $user_name);
$| = 1;
# print "User needs to be deleted - ";
# print "90 days old or older\n";
print "Delete Em!\n";
print "Verifying Last Login: ";
$wtmpx_user = grep(/$user_name/, @wtmpx_uv);
if ( $wtmpx_user == 0 ) {
print "No Entries in $wtmpx_file_loc\n";
} else {
@wtmpx_user = grep(/$user_name/, @wtmpx_uv);
$verified = $wtmpx_user[$#wtmpx_user];
print "$verified\n";
}
open(SENDMAIL, "|/usr/lib/sendmail -t");
print SENDMAIL "From: System Administration\n";
print SENDMAIL "Reply-To: sec_account\@xyz.com\n";
print SENDMAIL "To: $user_name@\xyz.com\n";
print SENDMAIL "Subject: $user_name Account Termination Notice\n";
print SENDMAIL "$user_name,\n";
print SENDMAIL "\n";
print SENDMAIL "\tWe found that you haven't logged into your account on unix server $hostname within the last 90 days.\n";
print SENDMAIL "\n";
print SENDMAIL "\tYour account has been terminated. If you still require this access, please send email to the system admin team.\n";
print SENDMAIL "\n";
print SENDMAIL "Thanks,\n";
print SENDMAIL "\tSystem Administration\n";
close(SENDMAIL);
} else {
print "OK\n";
}
}


, Mike