package Idle;

use strict;

BEGIN {@main::Ids = (@main::Ids, '$Id: Idle.pm,v 1.6 2001/11/25 06:35:25 ingolia Exp $ ');}

return 1;

# Return a hash whose keys are users logged in on the host with associated
#   values of shortest idle time for that user.
sub users_idle_times {
    my $hostname = shift || die 'No hostname specified';
    
    return idle_times($hostname, user_list($hostname));
}

# Return a hash whose keys are users logged in locally (on X logins) on the 
#   host with associated values of shortest idle time for that user.
sub local_users_idle_times {
    my $hostname = shift || die 'No hostname specified';

    return idle_times($hostname, local_users($hostname, user_list($hostname)));
}

# Return a list of all distinct users reported by finger at a hostname
sub user_list {
    my $hostname = shift || die 'No hostname specified';
    
    my $response = do_finger("\@$hostname");
    
    if ($response =~ /No one logged/) {
	return ();
    }
    
    my @resplines = split(/\n/, $response);
    my %users;
    
    foreach my $line (@resplines) {
	my $username;
	
	if ($line =~ /^\[/ or $line =~ /^Login/) {
	    next;
	}

	($username) = ($line =~ /^(\w+) /);
	
	if (defined($username) and $username ne 'Login') {
	    $users{$username} = '';
	}
    }

    return keys(%users);
}

# Filter a list of users on a hostname for those logged in on an X login, 
#   (something of the form :\d+\.\d+), returning a sublist.
sub local_users {
    my $hostname = shift || die 'No hostname specified';

    my @localusers;

    foreach my $username (@_) {
	my $response = do_finger("$username\@$hostname");
	
	if ($response =~ /:\d+\.\d+/) {
	    @localusers = (@localusers, $username);
	}
    }

    return @localusers;
}

# Get the idle time strings for a specified user on a specified host.
sub user_idle_strings {
    my $hostname = shift || die 'No hostname specified';
    my $username = shift || die 'No username specified';

# Two different finger formats: one doesn't give you all the logins unless
#  you specifically finger the user; the other gives you an unparseable 
#  long format when you finger a specific username, but gives you all
#  logins when no username is specified.
    my $response = do_finger("$username\@$hostname");

    if ($response =~ /Login:/ or $response =~ /Login name:/) {
	$response = do_finger("\@$hostname");
    }

    my @resplines = split(/\n/, $response);

    my $idleidx = index($resplines[1], 'Idle');
    if ($idleidx < 0) {
	die('No idle time in header ' . $resplines[1]);
    }

    my @idletimes;

    foreach my $line (@resplines) {
	if ($line =~ /^$username/) {
	    my $idletime = substr($line, $idleidx - 1, 5);

# Linux machines have an always-unidle console login that has a
#   Tty of *:1.
	    if ($line =~ /\*:\d+/ and $idletime =~ / $/) {
		next;
	    }

# Otherwise, a blank idle time or a dash indicates zero idle time.
	    if ($idletime =~ / $/ or $idletime =~ /-$/) {
		$idletime = '0';
	    }
	    @idletimes = (@idletimes, $idletime);
	}
    }    

    return @idletimes;
}

# Get numeric idle times in minutes for each of a user's logins on a host.
sub user_idle_times {
    my @idlestrs = user_idle_strings(@_);

    return map { parse_idle_string($_) } @idlestrs;
}

# Get shortest idle time for each user specified on a host and return a hash
#   of username keys and shortest idle time values.
sub idle_times {
    my $hostname = shift || die 'No hostname specified';

    my %useridles;

    foreach my $username (@_) {
	my @idletimes = sort {$a <=> $b} user_idle_times($hostname, $username);

	$useridles{$username} = $idletimes[0];
    }

    return %useridles;
}

# Parse an idle time string as returned by finger.  Formats are as shown
#   below, with capital letters as syntactic variables and +'s indicating one
#   or more; all syntactic variables bind decimal digits.
#   H+:MM hours and minutes
#     H+: hours
#     D+d days
#      M+ minutes
sub parse_idle_string {
    my $idlestr = shift;

    my $hrs;
    my $mins;
    my $days;

    if (($hrs, $mins) = ($idlestr =~ /(\d+):(\d\d)$/)) {
	return $mins + $hrs * 60;
    } elsif (($hrs) = ($idlestr =~ /(\d+):$/)) {
	return $hrs * 60;
    } elsif (($days) = ($idlestr =~ /(\d+)d$/)) {
	return $days * 60 * 24;
    } elsif (($mins) = ($idlestr =~ /^ *(\d+)$/)) {
	return $mins;
    } else {
	die('Unparseable idle time ' . $idlestr);
    }
}

# Run finger on a specified argument, grabbing standard error and 
#   dieing if an error is reported, otherwise returning the output.
sub do_finger {
    my $arg = shift || die 'No argument to finger';

    my $response = `finger $arg 2>&1`;

    my $error;

    if (($error) = ($response =~ /finger: ([^\n]+)\n/)) {
	die('finger error, ' . $arg . ', ' . $error);
    }

    return $response;
}
