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,
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
linux unix internet technology
Thursday, February 7, 2008
Shell Script To Disable and Delete User Accounts - Follow Up
Thursday, December 6, 2007
Taking a Look Inside Solaris' wtmpx
This is a Bonus Post, for those admins among us who aren't satisfied with the kind of information you can get from Solaris' standard commands that use /var/adm/wtmpx (like "last"). This is in line with the script we're also building up, in this post, to try and determine whether or not users have logged in within an arbitrary period of time and automatically lock up accounts and/or delete them completely based on that information.
I think this is still one post per day; so why am I calling it a Bonus Post? Partially because it was the first thing that came into my mind when I was trying to describe this blog-post and partially because the Perl scriplet that we're going to look at today is made up of constructs and functions that are what they are, and nothing more. I checked around and saw that there are variations on this script on other sites; mostly because there's only one un-convoluted way to do it (Even the argument to pack can't vary when tearing open the wtmpx structure(A32 A4 A32 l s s2 x2 l2 l x20 s A257 x). For that reason, I can only take credit for slapping together these predefined functions in a way that's slightly different than most. Some of it's in the coding and some of it's in what I'm trying to get out of the /var/adm/wtmpx file (nobody else seems to want it). This process is so rigid, I can no more take credit for it than I could take credit for inventing the "while loop" ;)
What we're looking for here is the "year." Yes, the "year." It's one of those things that I've never been able to figure out: Why was it left out of the "last" command? Great confusion can come of trying to pull auditing information and only knowing the months and dates people logged on. The "year" just seems like it should be included by default. I have no answer as to why it's not, but I do have a way for you to get it. Except for the initial command line, I've left out the error checking to save on space.
Note that the parts of the unpacked /var/adm/wtmpx are all the same, as well. We can change the names of the variables, but when you get right down to it, the file is composed of parts, and those parts are parts of a predefined structure. Note that in the below script, I'm looking for a user name and a year, like so:
$wtmpx_username holds the user name variable( I tried to name my variables in this script so that I would be reminded of what they represent later, when I've long since forgotten). Note that if we were coding this in "c" and accessing the actual utmp struct it would be a lot more work to make the variable names more easily memorable. The great thing about Perl is that it mimics the "c" behaviour but is actually only passing you an array of individual variables, so you can call them whatever you want.
$wtmpx_seconds holds the number of seconds since the epoch at which the login occurred. We pass that number to the Perl localtime() function, and it, in turn, can be parsed out to provide the "year!" If you want to read more about using the localtime() function in a script, check out this previous post.
That being said, you can find lots of different things inside /var/adm/wtmpx, like $wtmpx_line_device (usually the tty/pty), which can be compared against the string "system boot" to figure out every time your system has been rebooted. Of course, for the most part, the "last" command, and it's companions, get you all the information you really need. But it's nice to know you can get more specific data if you want it :)
Below; the Bonus Post Perl scriplet, and a promise to dive into understanding the pack and unpack functions in a future post!
This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#!/usr/bin/perl
#
# 2007 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
if ( $#ARGV != 0 ) {
print "Usage: $0 accountName\n";
exit(1);
$account_name = $ARGV[0];
$recordsize = length(pack("A32 A4 A32 l s s2 x2 l2 l x20 s A257 x",( )));
open(WTMPX,"/var/adm/wtmpx");
while (read(WTMPX,$record,$recordsize)) {
($wtmpx_username,$wtmpx_userid,$wtmpx_line_device,$wtmpx_process_id,$wtmpx_type,$wtmpx_termination_status,$wtmpx_exit_status,$wtmpx_seconds,$wtmpx_useconds,$wtmpx_session,$wtmpx_syslen,$wtmpx_hostname) = unpack($template,$record);
if ($wtmpx_username eq "$account_name"){
print "$account_name ".scalar localtime($wtmpx_seconds)."\n";
}
}
, Mike
linux unix internet technology

