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!

Creative Commons License

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


# 2007 - Mike Golvach -
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

if ( $#ARGV != 0 ) {
print "Usage: $0 accountName\n";

$account_name = $ARGV[0];
$recordsize = length(pack("A32 A4 A32 l s s2 x2 l2 l x20 s A257 x",( )));

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