Cover V10, I07
Article
Listing 1

jul2001.tar


WTMP: Another Step Toward Centralizing Event Management

Joe Aguiar

This article addresses yet another aspect of centralized event logging and management integrating data from the UNIX wtmp file into the the syslog text files. Because wtmp is not a text file, the typical shell commands are of limited use when examining its contents. I will discuss how to examine and integrate the data with the UNIX syslog utility for processing with familiar tools using a package written in C called logutmpd.

Although most of this discussion applies both to the utmp and wtmp files, I will focus primarily on wtmp because new records are appended to wtmp, whereas with utmp records are as likely to be deleted as appended. You might think of the info in utmp as indicating the current state of things, while wtmp contains historical data.

Events Reported in wtmp

Any process may write to the wtmp file providing that process has the required write privileges (user write, where user is root). The most common entries are made from the init process. The processes that init starts are listed in the /etc/inittab file. A wtmp entry may be made for each of the processes that init manages. Other programs that may make wtmp entries include telnetd, login, and xterm, or anything else that may connect to ttys, alter run level, or set the system date.

The type of information you might find in wtmp includes login/logout times for specific ttys, users, and sometimes remote hosts. Also, changes in run level and boot times are typical information to find in wtmp. Clearly this type of information can be of great interest to a systems administrator, particularly in regard to security management.

The amount and type of information found in wtmp can vary quite a bit from system to system. Some systems feature an extended wtmp file called wtmpx. This is true of Solaris and other SVR4-based systems. In fact, under Solaris, there is a wtmp file as well as a wtmpx file. The utmp struct that defines the records in both the utmp file and the wtmp file may be found in the file /usr/include/utmp.h, or perhaps /usr/include/utmpbits.h under Linux. This file, or actually the C struct statement within it, will describe what pieces of information are potentially available from each record of the wtmp file. Listing 1 shows an example of a typical utmp struct and the type of data to be had from it.

The only items that may not be obvious are ut_type and ut_exit. The ut_type describes the type of entry (e.g., boot time, run level, login process, etc.) These are described in the header files mentioned previously. The e_termination value is the signal number that caused termination of the child and e_exit is the exit code of the child. Your system man pages for utmp/x(5) as well as wait(2) should shed additional light on this.

Reading Data from wtmp

Because wtmp is a fixed-length record, binary style, data file, we cannot easily use normal text-based tools to look at its contents. Tools like last are extremely useful to examine wtmp data. However, it may not be easy or efficient to use last to examine wtmp in real time. Most UNIX systems provide the getutent(3), setutent(3), utmpname(3), and endutent(3) system calls to read and manipulate wtmp/utmp entries. Under Solaris, there are corresponding system calls for managing the wtmpx file.

The logutmpd daemon uses these, or alternatively, customized versions of some of these calls to continuously get at the latest entry in the wtmp file.

Using Logutmpd

The whole purpose of logutmpd is to allow the sys admin to efficiently and continuously monitor the wtmp file for new entries, convert them into the desired text-formatted message, and forward that message to the local syslog where the event may be easily viewed or acted upon.

To do this, logutmpd, upon startup, opens the system wtmp file. It then traverses the wtmp file one utmp struct record at a time until it reaches the end of the file. At this point, logutmpd waits for any new data to be written to wtmp. The interval that logutmpd waits (by default) is 1 second. The user may specify a different interval using the -us option to specify the number of microseconds, and -s to specify seconds. The timer used is based on the system select() call. When new data is detected it is read, reformatted, and written to the syslog at a priority of auth.notice.

By default, logutmpd runs as a daemon process and can be started at boot time using a normal UNIX startup script. The logutmpd distribution includes a sample Sys V-style init script. A command-line example of starting logutmpd might look like the following:

# logutmpd -us 500000 -f /var/log/wtmp
This would run logutmpd as a daemon process, reading from the file /var/log/wtmp, checking for new data every 500,000 micro-seconds (.5 seconds). Logutmpd may also be run in foreground or debug mode using the -d command-line switch. Using -d will prevent logutmpd from writing to the syslog. Instead, it will read all the data currently in wtmp, writing it out to stderr, and then write any new data to stdout. In this way wtmp data may be redirected to a text file or piped to another process, if desired. For instance, the following command would discard all the historical data and send any new data to the system console:

# logutmpd -d 2>/dev/null 1>/dev/console
The nice thing about having a daemon like this is to be able run this on all your remote hosts and let syslog do all the work of forwarding the data to your central log host. There is a tool called ulogger included in the logutmpd package that can be used to send messages to wtmp to test your installation (similar to using the logger command to send messages to syslog).

Customizing Logutmpd

Simple configuration changes to logutmpd may be accomplished at build time by defining certain macros using Makefile variables. More complex customization of logutmpd can be done by modifying the macros defined in the logutmpd.h file.

Let's examine what can be changed easily by modifying the Makefile. To change a configuration in the Makefile, we will need to change how the DEFINE variable is set. For instance, if our operating system is Digital UNIX, the line in the Makefile should read:

DEFINE = -DOSF
This has the effect of setting the OSF preprocessor macro at build time, which will ideally set things up for a proper build of the logutmpd binary on a Digital UNIX system. Other build options that are easily set up in the Makefile include the syslog priority that will be used for new messages and a flag to include the time data from the wtmp record.

Because syslog priorities have two components (a facility and a level), you must define the UT_FACILITY and UT_LEVEL macros to change the default logutmpd behavior. The default is LOG_AUTH and LOG_NOTICE, respectively. If you were to set the DEFINE variable in the Makefile, it might look like this:

DEFINE = -DUT_FACILITY=LOG_USER -DUT_LEVEL=LOG_WARN -DOSF
After rebuilding, each new message would now be sent to syslog at the user.warn priority. To investigate other possible settings for priority, check out the /usr/include/syslog.h file. syslog priorities are pretty standard, but there are subtle differences between systems. To include the time data from wtmp record in your syslog messages, simply append -DUT_TIME to the DEFINE variable definition in the Makefile.

Logutmpd is known to run on Linux, Solaris, and an ancient version of Digital UNIX. If you want to run this on a different platform, the easiest way may be to copy the set of macros for a system that comes close. Change the outermost #ifdef statement to reflect your OS, and customize the remaining macros as needed. The OLIST and UT_FORMAT macros are probably all that will be needed in the way of customization.

Suppose the utmp struct on your operating system only has two pieces of data: ut_user and ut_time. The OLIST might be changed from:

#define OLIST u->ut_user, u->ut_line,
u->ut_id, u->ut_pid, u->ut_host
to:

#define OLIST u->ut_user
and the corresponding UT_FORMAT from:

#define UT_FORMAT "%s, %s, %s, %d, %s\n"
to:

#define UT_FORMAT "%is\n"
The T_UT_FORMAT may require changing as well; the only difference is the time is placed at the front of the macros with the T_ at the beginning. The ut_time data will be included automatically if the macro discussed above has been set. Also, you have the option of building logutmpd using the systems *utent() calls or the custom ones supplied with logutmpd. Use whichever works best with your system. Don't forget to include -DYourOSName when setting DEFINE in the Makefile so your new macros will be used when building. That's about all that should be needed to get things working on your system.

Exploring Alternatives

There must be many alternative methods of centralizing all the wtmp data. One interesting approach is discussed in UNIX System V Network Programming by Stephen A. Rago, in which the author provides examples in the use of the ONC RPCs XDR to format the wtmp data before transferring it to a central host where it is imagined that the data can then be easily interpreted with another application since all the data, no matter what type of processor it came from, is in a uniform, XDR representation.

Another, perhaps simpler, approach is to write a Perl script that uses the unpack() function to read the data. One such script is shown in recipe 8.18 of Perl Cookbook, by Tom Christianson and Nathan Torkington. The unpack() argument format can be tricky to get right. Because it is based on the utmp struct, it may vary a bit between operating systems. You will need to "use Sys::Syslog;'' and add the openlog(), syslog(), closelog() calls. Also, to get it to run as a daemon, you need to add code similar to:

sub daemon_init {
    my $cpid;
    if ( !defined( $cpid = fork())) {
        perror( "Cannot fork a new process.");
        exit( -1);
    } elsif ( $cpid != 0) {
        exit( 0);
    }

    open( STDOUT, ''>/dev/null'');
    open( STDERR, ''>&STDOUT'');

    setsid();    # use POSIX;
    chdir( "/");
    umask( 0);

    return( 0);
}
Using this method may give you a slight performance hit compared to the C-based solution. On the other hand, if you have Perl on all of your hosts, this might be a way to go.

Conclusion

UNIX has a long history and exists in many forms. As a result, achieving consistent and complete logging across platforms can be a really interesting project. I hope the tools presented here will help you achieve an extra step toward that goal.

The latest version of the logutpmd package may be downloaded from http://home.inreach.com/jaguiar. Questions or comments should be addressed to jaguiar@inreach.com.

References

1. Rago, Stephen A. UNIX System V Network Programming, 1993, Addison-Wesley.

2. Christianson, Tom and Torkington, Nathan. Perl Cookbook, 1998, O'Reilly & Associates.

3. Stevens, W.R. Advanced Programming in the UNIX Environment, 1992, Addison-Wesley.

4. UNIX Manual pages for wait(2), utmp(5), init(8), getutxent(3C).

Joe Aguiar has worked as a Systems and Network Administrator and Software Engineer since 1986. He holds a BSEE degree from CSU, Fresno. He can be reached at jaguiar@inreach.com.