Cover V02, I06
Listing 1
Sidebar 1
Sidebar 2
Table 1
Table 2
Table 3


Timing Out Idle Users

Larry Reznick

All users should logout when they leave their workstations for an extended period of time or when they leave at the end of the day. A workstation with a user logged in but not present is an invitation to a security breach. System administrators don't have the time to watch what everybody is doing and finger-wag when users forget to logout. Besides, after a certain amount of finger-wagging, the system administrator loses credibility with the users.

The system knows how active the user's terminal is because the system has to keep up with all of that activity. You could make the system monitor the users' activities and, if one of the terminals is idle for longer than a specific time, force the user to logout. SCO UNIX has a special program for this, named idleout. SVR4 has no such program, but a simple shell script can do the same work.

SCO's idleout

Run the idleout(ADM) program only once. idleout monitors user activity on all tty lines to see when a line has been inactive for longer than the time you specified. Name the idle time in either minutes or hours:minutes. For instance, use

idleout 20

to force a logout on any tty line inactive for more than 20 minutes, and use

idleout 1:30

to force a logout on any line inactive for more than an hour and a half. You could also use

idleout 90

to force that same hour-and-a-half timeout. If you don't provide an idle time threshold, idleout uses the default time in the textfile /etc/default/idleout. The textfile sets an environment variable named IDLETIME. The default setting is 120, or two hours.

If you execute idleout manually, as root of course, it forks a shell, detaches from the shell you launched it from, and sleeps for 60 seconds. When it wakes up, it looks at the tty lines to see if any of them have been idle for longer than the interval you named. If so, it kills the offending process.

The idleout program never finishes, even if it kills all idle tty processes. You can't just turn it off. It keeps watching the tty processes, checking them once a minute and killing any subsequent idle tty processes that come along. If you, as root, want to kill the idleout process, you must check the process status list using ps(1) and find the "sleep 60" process. You could track that process back to its parent process ID (ppid) and find the shell, usually identified as "-sh" in the ps list. Then, kill the shell and the sleep process. But rarely would you run idleout manually. Instead, start it automatically every time you boot UNIX.

Bootstrap idleout

Monitor the tty idle time with every reboot. Install idleout in the bootstrap rc files. The /etc/rc* files are shell scripts containing the run commands executed with every system startup or shutdown. There is a primary file named /etc/rc; other files are named for the various system run levels:

/etc/rc0	Init level 0 for system shutdown
/etc/rc1	Init level 1 for single user mode
/etc/rc2	Init level 2 for multiuser mode
/etc/rc3	Init level 3 for networked multiuser mode

These scripts refer to the contents of one of the following directories:

/etc/rc.d		Contains common run command directories
/etc/rc0.d		Contains init level 0 run commands
/etc/rc1.d		Contains init level 1 run commands
/etc/rc2.d		Contains init level 2 run commands
/etc/rc3.d		Contains init level 3 run commands.

The directories named after the init levels represent the usual way UNIX SVR4 handles the run commands. The file names in those directories begin with an S or a K to identify scripts that start or kill processes. A two-digit sequence number that follows the S or K identifies which file in the S or K group must run first. The rest of the file name generally identifies what the script does. For example, Table 1 shows a sample rc2.d directory's contents, and Table 2 shows a sample rc0.d directory's contents.

Another method for handling the run commands uses the rc.d directory. The rc.d directory contains subdirectories named with single digits from 0 to 9, representing the execution sequence of all scripts found in those directories. Put a script in that directory to execute it during system startup or shutdown.

Figure out which method your system uses and change either /etc/rc.d/8/userdef or /etc/rc2.d/S88USRDEFINE to start idleout with the number of minutes you want to allow.

killidle Script

If you don't have SCO UNIX, you can still kill idle tty processes with a shell script. The script combines who(1) and awk(1). The who command identifies the ttys in use and their idle times. The awk script analyzes who's output, decides which ttys are too old, and puts them out of our misery. Listing 1 shows the killidle script.

The colon in the first line forces a Bourne shell to execute the script. The first code line,


sets the IDLEOUT variable to either the value in killidle's first command line argument or 20. Instead of using if tests to see whether there is a $1 argument, I use the shell's own variable substitution mechanism. This syntax says that if $1 has a value, use it. If there is no $1 or it has a zero value, use the 20. Whichever is selected, assign the result to the variable IDLEOUT.

The next step avoids trouble if the user gives a negative time. I chose to set IDLEOUT to the default 20 minutes if a negative time is discovered. As another approach, you might use expr to change the value's sign.

With IDLEOUT valid, the script uses the who command to get the user list. The -u option limits the output to only those people currently logged in. It also shows the idle time for each of those logins. The code pipes the output to the awk program to process that idle time.

The awk script doesn't contain a pattern, just an action. To keep things easy, it extracts the column values needed for analysis and output: the user's name, the terminal ID, the idle time, and the login's process ID.

If a user has pressed a key at the tty within the last minute, that tty's idle column shows a period. The script's first if test eliminates that condition from the script's concern. Given some idle time amount, who shows it in the format hours:minutes. The hours may be set to zero. Only the minutes are needed for this script.

An easy way to isolate the minutes is to treat the colon as a field separator and split the idle time into an array. Awk's split function does that work. split's first parameter is the string to be split. The second is the array to put the split results into. The third is the field separator. split creates the space for the second parameter if the array doesn't already exist. awk subscripts start at 1, so idletime[1] holds the hours and idletime[2] holds the minutes. If the minutes idle figure is greater than that specified in the IDLEOUT variable, it is time to kill the process.

Assuming that a warning message for killed processes might be helpful, a print statement identifies which user on which terminal is being logged out, how long the terminal was idle, and the pid for debugging the script. This output not only aids in debugging, but also allows you to build a log file from the output to identify users who tend to leave the terminal idle long enough to create potential security breaches.

Having identified which pid has been idle too long, the awk script uses its system function to send a kill signal to the pid. The signal number 9 is the kill signal that can't be ignored by an application and can't be intercepted to make it act differently. The only process that can withstand a signal 9 is a process trapped in the kernel. Processes trapped in the kernel are often known as zombie processes, because they live on after being killed -- they just don't want to die.

Sending the signal 9 is an extreme measure. Its main advantage is that it can't be intercepted and it can't be ignored. When a process receives this signal, no matter what it was doing, it ceases. That means any cleanup actions it should have done won't get done. You might find it nicer to send a signal 15 instead. A process can intercept signal 15 and trigger its cleanup operations. Another nice signal to send is 1, the hangup signal. The terminal will think there's no connection any more and should terminate itself. These are weaker signals, though, and a process can choose to ignore them. The signal 9 gets through no matter what, except for those pesky zombies.

At the end of the script is the funny-looking syntax,


Notice that this appears on the awk command line after the script has finished. awk doesn't use the shell's environment variables directly. Getting a shell variable or any other value into an awk variable requires the use of an assignment on awk's command line. I chose to use the same name as the shell variable to keep references simple.

Several worthwhile changes would include:

  • accommodating the keyword old in the who's idle column for idle times older than 24 hours

  • handling the full hours:minutes format for a large number of minutes requested, such as 120, or for an hours:minutes request format, such as 1:30

  • working from a table identifying certain users with different idle times, allowing the fine-tuning of the idle times for certain special users.

    Running killidle

    You can run killidle any time manually. Just type


    to use the default 20-minute idle time, or type

    killidle minutes

    where minutes is any positive number for the idle minutes allowed.

    The script looks through the who output and kills any logins idle for too long. If you're not root, though, you can only kill your own processes. The output messages tell you who else has been idle too long, but you can't do anything about it unless you're root.

    Of course, running killidle manually is a bit silly. You'd have to remember to run it periodically, and it wouldn't be much of a security monitor run if you forget to run it. If you run it from root's crontab, you can set it to run around the clock. As a root cron job, it has root's privileges, so it could kill other login processes without restriction. Finally, since all cron command output is automatically captured and mailed to the user whose crontab is executing, root would receive a mail message containing all of the timeout warnings for each killidle run.

    The simplest way to put killidle in the root crontab is to use the line

    * * * * * /usr/local/bin/killidle

    assuming that /usr/local/bin is the directory holding the killidle script. The five asterisks tell cron to run killidle once a minute every minute of every day cron is up. That could be extreme, but it would get the job done. To reduce the script's impact on system resources, run it only a few times an hour.

    For example, the crontab line

    0,15,30,45 * * * * /usr/local/bin/killidle

    checks for idle ttys every 15 minutes. Considering that the script's default is 20 minutes idle, the worst case idle time would be 29 minutes for a login started one minute after cron's last run. Fourteen minutes later cron runs again, but the tty has run for only 14 minutes, which is less than killidle's 20, so killidle leaves it alone. Fifteen minutes later, a total of 29 minutes, cron runs killidle again and catches the old tty.

    You could run killidle with a 15-minute option. If you do that, however, the same timing problem appears. A login that is only 14 minutes old when cron runs killidle would survive this run. killidle would eliminate the login 15 minutes later if the login were still idle. That login would have survived for a total of 29 minutes.

    You could set killidle to test for an interval less than the interval cron runs. For example,

    0,20,40 * * * * /usr/local/bin/killidle 19

    kills anything 19 minutes old or older. Because cron runs every 20 minutes, a login that started one minute after the last cron run is 19 minutes old, qualifying for the kill. You're still not eliminating the problem completely. If a tty starts two minutes after the cron check, it will be only 18 minutes old, and thus will pass the 19-minute test. Another 20 minutes passes before the cron job starts killidle again, allowing a total of 38 minutes of idle time.

    How long you let idle logins run depends on what you're willing to live with. On the one hand, it doesn't take very long for someone to use an account left idle to break into the system. On the other hand, you're not going to please everybody. Some programmers, for instance, work at their desks near the terminal and are not on the terminal every moment. It isn't nice to log them out while they've got important facts on the screen and are busy looking through a listing. This is where a table of user idle times that can be used by killidle comes in handy. You must find a balance between security and people who need their temporary login environment changes and screen displays to remain intact when they're only away from the terminal for a moment. You decide how long a moment lasts.

    About the Author

    Larry Reznick has been programming professionally since 1978. He is currently working on systems programming in UNIX and DOS. He teaches C language courses at American River College in Sacramento. He can be reached via email at: rezbook!