I found Larry Reznick's article "Timing Out Idle
Users" (Nov/Dec
1993 Sys Admin, vol. 2, no. 6, p. 43) extremely interesting
and valuable. When I read it, I was in the process of
porting some
C code to do the same task and was making no progress.
The program
I was porting did have some limitations that I hoped
to overcome later,
but I preferred Reznick's approach using a shell/awk
script. For my
purposes, I decided to update his original work.
The original killidle script scanned the /etc/utmp
file using the who(1) command. When it spotted a terminal
idle for longer than a predetermined time period, it
sent a SIGKILL
(-9) signal to the process ID (pid) and logged the action.
I've modified the algorithm a little to scan the /etc/utmp
file using the w(1) command -- more on why later --
for each login. I then look up the amount of idle time
allotted for
the user, and if the amount of idle time is greater
than or equal
to the warning time, I send a warning message to the
login. If the
idle time is greater than or equal to the maximum idle
time allocated
to the user, I send a message to the login about the
impending death,
issue the kill via a SIGHUP (-1), which is a safer signal,
and log the kill action.
getparms (Listing 2) is a simple shell script that looks
up
a user and returns the maximum amount of idle time allotted
and the
amount of idle time allowed before a warning should
be issued. The
script simply greps a priv.users file for the login
name and
returns two fields in the record found: a maximum timeout
minutes
value and warning minutes value. If the login is not
in the file,
getparms looks for the keyword "DEFAULT" and
returns
its values.
Figure 1 shows a sample priv.users file. In this sample,
the
user "mrg" gets 999,999 minutes (over 694
days) of idle time
before being warned or knocked off the system. The user
"erc"
gets warnings after 540 minutes (9 hours) and killed
after 1,440 minutes
(1 day) of idle time. All other users get warnings after
15 minutes
and killed after 30 minutes of idle time.
I chose the w command over the who for two reasons.
First, I wanted to support idle times over 24 hours.
Comparing Figures
2 and 3, you can see the difference in the idle times.
With the who
command (Figure 3) idle times greater than 24 hours
are indicated
by the keyword "old." The w command (Figure
2) shows
the number of days the login was idle. Compare, for
example, the two
entries for pts/2.
Second, I wanted to warn my users that they've been
idle too long
and, if the session goes away, tell them why. If you
read the man
pages for both commands closely, you'll see a subtle
difference in
the interpretation of the idle field. The w command
uses keystrokes
to determine how long you've been sitting idle -- if
you don't
touch the keyboard for 10 minutes you're idle for 10
minutes. The
who command, on the other hand, uses screen activity
as an
indication of idle time. So if a program displays a
message on a terminal,
that terminal's idle time would reset to 0 minutes even
though nothing
was typed. It is for this reason that I chose to create
the wruser
program (Listing 3) to display a message on the terminal.
If I had
used a conventional approach (i.e., written "some
warning"
> /dev/pts2"), then it would have been perceived
as terminal activity,
and the user idle time would be reset to zero. [Editor's
note: w
is a BSD command. It is included in many versions of
UNIX, but not
all. If your version doesn't have w, try whodo -l
as a substitute.]
Listing 1 shows the main program, idleout. You'll need
to
change the variable localpath to the directory where
you plan
to keep the getparms script and the wruser program
(more on wruser later). The first thing idleout does
is look up each user in the file priv.users using the
getparms
script. This lookup sets two idleout variables: warntime
and killtime. warntime is the amount of elapsed idle
time before warning messages are sent to the user; killtime
is the amount of elapsed time before the user is removed
from the
system.
Next, idleout takes the idle field from the w command's
output and converts it into a numeric value expressed
in minutes,
idletotal. If the total idle time is greater than the
killtime,
wruser sends a message to the user's terminal telling
of the
login's imminent death, the terminal is killed, and
a kill log file
is updated. If the total idle time is greater than the
warntime
but less than the killtime, idleout sends a warning
message to the terminal.
The wruser program takes two parameters, the terminal
to write
to, without the /dev, and the message to be displayed.
After
recombining the message it searches through /etc/utmp
looking
for a match for the terminal (ut_line holds the terminal).
Once the match is found, the /dev/ is prepended and
the process
ID is stored. The write() function displays the message,
and
the pid is both printed and returned.
This is where it starts to get a little complicated.
I needed to execute
wruser from idleout's awk script and receive
wruser's return value back into the awk script. To
accomplish this, I form the command using sprintf, issue
the
command, and pipe the output to awk's built-in getline
function. getline reads the next line of input, in this
case
the printed output piped from wruser, and puts the result
in awk's pid variable.
idleout is designed to run as a root cron job. It
must run with root authority to allow wruser to open
the terminal.
Currently I run it every 2 minutes with the cron entry:
0,2,4,6,8,10,12,14,16,18, \\
20,22,24,26,28,30,32,34,36,38, \\
40,42,44,46,48,50,52,54,56,58 * * * * (sh /usr/user/idleout)
One last feature that's not in this version is the ability
to dynamically
change a user's killtime and warntime. The file priv.users
can be updated any time and the next time the script
is run, the new
file will be used. I'd like to see killtime and warntime
change according to what application the user is running.
For example,
an archie search can take over 20 minutes. With no keyboard
activity the session could be terminated due to a perceived
idle condition.
But that's for a future day.
About the Author
Matt Ganis is currently an Advisory programmer for
the Advantis
corporation, located in White Plains, NY where he works
on multi-protocol
networking. In his spare time he teaches Astronomy at
Pace University
in Pleasantville, NY. He can be reached at 14 Udell
Ct, Cortlandt
Manor, NY 10566 or ganis@vnet.ibm.com.