系统管理工具包: 监视用户的使用情况

研究在各种不同的日志中记录 UNIX® 登录和其他系统活动的新方法,并利用这些信息来监视用户的使用情况。从很多角度来看,这样做是非常有帮助的,可以用于进行退款报告,或者仅用于了解系统中个别用户的繁忙和活跃程度,以便在进行规划和分配资源的时候能够起到帮助作用。

Martin C Brown, 自由撰稿人和顾问, MCslp

Martin Brown 成为一名职业作家已经有 8 年多的时间了。他是很多书籍和文章的作者,内容涉及很多主题。他的特长包括很多开发语言和平台 —— Perl、Python、Java、JavaScript、Basic、Pascal、Modula-2、C、C++、Rebol、Gawk、Shellscript、Windows、Solaris、Linux、BeOS、Mac OS/X 等等 —— 还包括 Web 编程、系统管理和集成。他会定期为 ServerWatch.com、LinuxToday.com 和 IBM developerWorks 撰写文章,在 Computerworld、Apple Blog 以及其他站点都会定期更新自己的 blogger,同时还为 Microsoft 撰写一些主题文章。您可以通过 questions@mcslp.com 与他联系。



2007 年 11 月 23 日

关于本系列

典型的 UNIX® 管理员拥有一套经常用于辅助管理过程的关键实用工具、诀窍和系统。存在各种用于简化不同过程的关键实用工具、命令行链和脚本。其中一些工具来自于操作系统,而大部分的诀窍则来源于长期的经验积累和减轻系统管理员工作压力的要求。本系列文章主要专注于最大限度地利用各种 UNIX 环境中可用的工具,包括简化异构环境中的管理任务的方法。

获取当前用户的信息

要获取当前使用 UNIX 系统的用户的列表,可以使用很多不同的方式。其中最显而易见、且最直接的方式是使用 who 命令。who 命令可以返回一个列表,该列表中包括当前登录的用户、他们所连接到的终端、他们登录的日期,以及他们登录的主机名的 IP 地址(如果他们是远程用户的话)。

您可以参见下面清单 1 中给出的示例。

清单 1. 使用 who 命令,以返回当前登录用户的列表
$ who 
mc         pts/2        Sep 12 14:29    (sulaco.mcslp.pri)
mcbrown    pts/3        Sep 12 14:37    (nautilus.mcslp.pri)

在某些系统中,还可以使用 -q 命令行选项,以提供一种更快捷的格式,如清单 2 中所示。

清单 2. 带 -q 选项的 who 命令
$ who -q
mc       mcbrown
# users=2

这个输出与某些系统中提供的、另一个名为 users 的命令的输出很类似,后者仅输出用户的列表,而不对其进行计数(请参见清单 3)。

清单 3. users 命令
$ users
mc  mcbrown

通常,您可以通过添加 -a-H 命令行选项来查找更详细的信息。-a 选项包括所有来自 /var/adm/utmpx 文件的最新信息,该文件用于记录登录信息。这个文件还可以记录其他事件,如启动的日期和时间,以及最近的运行级别更改。在本文稍后的内容中,您将进一步地了解该文件中存储的相关信息。-H 选项用于为输出中的各列添加标题信息。您可以参见清单 4 中给出的示例。

清单 4. 带 -a-H 选项的 who 命令
$ who -aH
NAME       LINE         TIME          IDLE    PID  COMMENTS
   .       system boot  Sep 12 11:35
   .       run-level 3  Sep 12 11:35     3      0  S
zsmon           .       Sep 12 11:35  3:14    215
LOGIN      console      Sep 12 11:35  0:20    221
LOGIN      console      Sep 12 11:35  0:20    510       (:0)
mc       + pts/2        Sep 12 14:29   .      569       (sulaco.mcslp.pri)
mcbrown  + pts/3        Sep 12 14:37  0:12    675       (nautilus.mcslp.pri)

通过开头的两行,您可以了解最近一次启动系统的时间,以及最近一次运行级别更改的日期和详细情况。这里所显示的计算机正在运行 Solaris,并且它使用 zsmon 守护进程来监视串行端口上的登录。然后,您将拥有两个到控制台的、标记的登录,其中一个是活动用户的登录,而另一个则附加到 X 服务器(:0 表示 X 窗口系统的屏幕参考)。

最后的两行是使用安全 Shell (SSH) 的远程登录。对于所有的活动进程,您都将获取一个进程 ID,这意味着您可以在 ps 列表中通过 ID 来识别原始用户。

LINE 列是用户所连接到的终端;控制台显然是该计算机的主键盘和监视器。pts 行指的是自动创建的、用以处理远程 SSH 连接的伪终端。

当然,了解谁正在使用本地计算机是一件很有趣的工作,但是对于网络中其他的计算机,情况又如何呢?

获取远程用户的信息

有两个后台服务可以提供远程用户的信息,它们分别是 rusersrwho,分别通过两个守护进程 rusersd(通常称为 in.rusersd)和 rwhod(也称为 in.rwhod)来实现。这两个服务都依赖于远程过程调用(Remote Procedure Call,RPC)协议,以便在整个网络的范围内共享信息。

以前一直认为,启用 rwhodrusersd 是对网络资源的浪费,因为这两个守护进程在网络中不断地广播信息,从而占用了带宽。在拥有大量主机的网络中,所生成的信息量可能是非常大的。现在,与网络的传输速度相比,所生成的信息量相对较小。

rwhodrusersd 在所有的系统中都是不可用的,但是对于那些提供了相应工具的系统来说,使用这些工具可以很容易地获取您所想要的信息。如果这些工具还没有运行,那么您需要启动 in.rwhodin.rusersd 守护进程。

一旦这两个守护进程开始运行,您就可以使用各种各样的工具输出所有计算机的相关信息。rwho 工具是 who 工具的远程、支持网络的版本。清单 5 给出了一个示例。

清单 5. rwho 工具
$ rwho -a
mc       solaris-desktop:console Sep 12 11:29  3:41
mc       solaris-desktop:pts/1   Sep 12 11:32   :06
mc       ultra3:pts/2            Sep 12 14:29
mcbrown  ultra3:pts/3            Sep 12 14:37   :37
M

rwhod 工具发出广播信息,并且捕获来自其他主机的广播信息,在 /var/spool/rwhod 目录中对其进行整理。

rusers 工具向网络上其他主机中的 rusersd 守护进程发送广播,以便返回当前登录的用户的信息,如清单 6 所示。

清单 6. rusers 工具
$ rusers
Sending broadcast for rusersd protocol version 3...
192.168.0.31 mc mcbrown
solaris-desktop. mc mc

rwhod 守护进程开始运行之后,还可以使用另一个工具 ruptime,该工具可以返回本地网络中所有运行 rwhod 的服务器的加载和正常运行时间信息。清单 7 显示了一个示例。对于确定多台计算机的状态,并且不希望逐个地对它们进行登录,这是一个非常合适的工具。

清单 7. ruptime 工具
$ ruptime
solaris-desktop  up     3:53,     1 user,   load 0.00, 0.00, 0.00
ultra3        up     3:46,     2 users,  load 0.00, 0.00, 0.00

对于 whorwho 来说,将当前连接到您的计算机的 who 信息记录到 /var/adm/utmp 文件中,而曾经登录(并已注销)的用户的历史信息则保存于 wtmp 计算机中。所有这些文件都包含了大量的信息,但是这些文件都没有可用的直接格式。让我们更深入地研究这些文件,并了解如何显示这些信息。

用户活动所使用的日志文件

有很多文件用于记录用户的登录和活动。用于记录这种信息的文件主要包括下面三个:

  • utmp,这个文件记录了当前登录的用户的相关信息。对于用户的每次登录,它都应该包含一条相应的记录。
  • wtmp,这个文件记录了用户进行的所有登录(和注销)。在一个繁忙的系统中,这个文件是相当大的,因为对于每次登录,它都包含一条登录和一条注销记录。这个文件还包含了一些系统相关的日志信息,如重新启动、关闭和日期更改。
  • lastlog,这个文件记录了每个用户最近一次登录的时间。对于每个用户,这个文件仅包含一条记录。

在您了解了这些文件是何时、采用何种方式写入的之后,那么这些文件之间的关系就相当直观了。基本顺序如下所示:

  • 当用户登录的时候,打开 lastlog 文件,并对他们的记录中的登录日期和时间进行更新。然后,打开 utmp,并记录他们的当前登录信息。将登录记录(通常将这些信息的副本添加到 lastlog)写入到 utmp 日志中,以记录相应的登录信息。
  • 在注销期间,删除写入到 utmp 的登录记录(因为该用户不再处于登录状态),但是将一条新记录写入到 wtmp,以记录该用户已经注销的事实。
  • 这些文件的格式都是二进制的;所以,必须使用一种单独的、能够读取和解析其中信息的工具来报告这些信息。通常,wtmp 和 lastlog 文件的基本格式是相同的,并且它们都使用一种简单格式来记录登录时间、登录行和主机名称行信息,如清单 8 中所示,其中显示了 Solaris 格式。
清单 8. wtmp 中的 Solaris 格式
struct lastlog {
        time_t  ll_time;
        char    ll_line[UT_LINESIZE];
        char    ll_host[UT_HOSTSIZE];
};

对于不同的操作系统,格式是不相同的,因此您必须非常小心地提取合适的信息。在 AIX® 中,清单 9 显示了 utmp 定义。

清单 9. utmp 定义
struct utmp
{
   char   ut_user[8];
   char   ut_id[14]                    
   char   ut_line[12];
   short  ut_type;
   pid_t  ut_pid;                       
   struct exit_status
   {
     short    e_termination;           
     short    e_exit;
}

清单 10 显示了记录的 ut_type 字段值的定义。

清单 10. ut_type 字段值的定义
#define EMPTY              0
#define RUN_LVL            1
#define BOOT_TIME          2
#define OLD_TIME           3
#define NEW_TIME           4
#define INIT_PROCESS       5
#define LOGIN_PROCESS      6

         
#define USER_PROCESS       7
#define DEAD_PROCESS       8
#define ACCOUNTING         9
#define UTMAXTYPE ACCOUNTING

因为是根据用户 ID 进行编码的,所以该记录中不包括用户名信息。如果登录用户的 ID 是 1000,那么可以通过访问位于 1000 x sizeof (struct lastlog) 的记录来定位对应的 lastlog 记录。

utmp 文件结构与其非常类似,但因为它是关于登录、注销、以及其他事件条目的顺序日志,所以该记录包括清单 11 中的登录信息。

清单 11. utmp 文件结构定义
struct utmp {
        char    ut_line[UT_LINESIZE];
        char    ut_name[UT_NAMESIZE];
        char    ut_host[UT_HOSTSIZE];
        time_t  ut_time;
};

Linux® ut_type 具有类似的值定义,如清单 12 中所示。

清单 12. Linux ut_type 定义
#define UT_UNKNOWN      0
#define RUN_LVL         1
#define BOOT_TIME       2
#define NEW_TIME        3
#define OLD_TIME        4
#define INIT_PROCESS    5
#define LOGIN_PROCESS   6
#define USER_PROCESS    7
#define DEAD_PROCESS    8
#define ACCOUNTING      9

还有一种更加复杂的结构定义,如清单 13 中所示。

清单 13. 更加复杂的 utmp 结构定义
struct utmp {
    short ut_type;              /* type of login */
    pid_t ut_pid;               /* PID of login process */
    char ut_line[UT_LINESIZE];  /* device name of tty - "/dev/" */
    char ut_id[4];              /* init id or abbrev. ttyname */
    char ut_user[UT_NAMESIZE];  /* user name */
    char ut_host[UT_HOSTSIZE];  /* hostname for remote login */
    struct exit_status ut_exit; /* The exit status of a process
                                   marked as DEAD_PROCESS */

/* The ut_session and ut_tv fields must be the same size when
    compiled 32- and 64-bit.  This allows data files and shared
    memory to be shared between 32- and 64-bit applications */
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
    int32_t ut_session;         /* Session ID, used for windowing */
    struct {
        int32_t tv_sec;         /* Seconds */
        int32_t tv_usec;        /* Microseconds */
    } ut_tv;                    /* Time entry was made */
#else
    long int ut_session;        /* Session ID, used for windowing */
    struct timeval ut_tv;       /* Time entry was made */
#endif

    int32_t ut_addr_v6[4];       /* IP address of remote host */
    char __unused[20];           /* Reserved for future use */
};

大多数系统都提供了一些简单工具,以便为您提取相关的信息。要转储 wtmp 文件的内容,可以使用 last 命令。该命令将转储所有的信息(请参见清单 14)。

清单 14. 使用 last 命令转储 wtmp 文件的内容
$ last
statmon   ftp      nautilus.mcslp.p Wed Sep 12 15:50 - 15:50  (00:00)
statmon   ftp      narcissus.mcslp. Wed Sep 12 15:50 - 15:50  (00:00)
statmon   ftp      nostromo.mcslp.p Wed Sep 12 15:50 - 15:50  (00:00)
statmon   ftp      sulaco.mcslp.pri Wed Sep 12 15:49 - 15:49  (00:00)
statmon   ftp      nautilus.mcslp.p Wed Sep 12 15:45 - 15:45  (00:00)
statmon   ftp      nostromo.mcslp.p Wed Sep 12 15:45 - 15:45  (00:00)
statmon   ftp      narcissus.mcslp. Wed Sep 12 15:45 - 15:45  (00:00)

通常,如果需要的话,您可以通过用户、主机或者 tty 对信息进行筛选。例如,要获取 root 所进行的所有登录,可以使用清单 15 中所示的内容。

清单 15. 获取 root 所进行的所有登录
$ last root
root      console      :0   Mon Sep 25 11:32 - 11:32  (00:00)
root      console      :0   Mon Sep 25 11:27 - 11:27  (00:00)
root      console           Sat Sep  9 13:17 - 13:28  (00:11)
root      console           Sat Sep  9 10:47 - 13:14  (02:26)
root      console           Sun Sep  3 06:52 - down  (6+03:54)
root      console      :0   Sat Sep  2 14:24 - down   (16:27)
root      console           Sat Sep  2 08:02 - down   (06:13)
root      console           Fri Aug 25 17:16 - down   (00:39)
root      console           Sun Aug 20 16:04 - 16:04  (00:00)
root      console           Thu Jul 20 07:23 - 07:31  (00:07)
root      console           Thu Jul 20 07:22 - 07:23  (00:00)
root      console           Thu Jul 20 02:57 - 02:57  (00:00)
root      console           Wed Jul 19 12:22 - down   (05:38)
root      console           Wed Jul 19 12:10 - 12:19  (00:08)
root      console      :0   Wed Jul 19 12:05 - 12:09  (00:04)
root      console           Wed Jul 19 11:47 - 11:55  (00:07)

wtmp begins Wed Jul 19 09:54

因为 wtmp 还包含关于重新启动和关闭的信息,所以您也可以列出这些信息(请参见清单 16)。

清单 16. 获取关于重新启动和关闭的信息
$ last reboot 
reboot    system boot                   Wed Sep 12 11:28
reboot    system down                   Wed Sep  5 12:16
reboot    system boot                   Mon Sep  3 13:03
reboot    system down                   Thu Mar  1 11:33
reboot    system boot                   Thu Mar  1 09:57
reboot    system down                   Thu Mar  1 08:12
reboot    system boot                   Thu Mar  1 08:05
reboot    system down                   Thu Mar  1 08:12
reboot    system boot                   Thu Mar  1 08:05
reboot    system down                   Thu Mar  1 08:03
reboot    system boot                   Thu Mar  1 08:02
reboot    system down                   Sun Dec 17 10:04
reboot    system boot                   Sun Dec 17 10:02
reboot    system down                   Mon Sep 25 11:44
reboot    system boot                   Mon Sep 25 10:2

前面介绍的这些工具都是十分有效的,但是如果您希望自己解析这些信息,那该怎么办呢?您可以编写一个简单的 C 程序以提取相关信息,但事实上,您还可以通过使用 Perl 的 unpack() 函数来完成这个任务。

解析二进制日志文件

在 Perl 中使用 unpack 函数需要创建适当的 packstring,它用于定义您希望从二进制记录数据(从原始二进制 utmp 文件中读取)中提取的数据类型。

清单 17 显示了一个很简单的 Perl 脚本,该脚本用于对 wtmp 中的数据进行直接转储。

清单 17. 转储 wtmp 中数据的 Perl 脚本
my $packstring = "a8a8a8ssssl";
my $reclength = length(pack($packstring));
my @ut_types = qw(EMPTY RUN_LVL BOOT_TIME OLD_TIME
                  NEW_TIME INIT_PROCESS LOGIN_PROCESS
                  USER_PROCESS DEAD_PROCESS ACCOUNTING);

open(D,"</var/log/wtmp") or die "Couldn't open wtmp, $!";

while(sysread(D,my $rec,$reclength))
{
    my ($user,$userid,$line,$pid,$type,$eterm,$eexit,$time)
        = unpack($packstring,$rec);
    print("$user, $userid, $line, $pid, $ut_types[$type], ",
          "$eterm, $eexit, ", scalar localtime($time),"\n");
}

close(D) or die "Couldn't close wtmp, $!";

请注意,您必须使用 sysread 来读取数据,因为您读取的是原始二进制包(而不是行)。您所使用的 packstring 将获取用户名、用户 ID、tty 线路、PID、utmp 类型、终端退出代码和时间。

最前面的三个字段是非常重要的,因为其中包含了行、名称和主机信息。在 utmp.h 中,对这些字段的大小进行了合适的定义(请参见清单 18)。

清单 18. utmp.h 中定义的最前面三个字段的大小
#define UT_NAMESIZE     8
#define UT_LINESIZE     8
#define UT_HOSTSIZE     16

请注意,您必须正确地获取数据的大小,因为它将影响到您所读取的信息。如果您使用的是 Solaris 或者 BSD 系统,那么它使用了一种更加简单的结构(如清单 8 所示),因此,您需要使用一个不同的 packstring 和提取的字段,如清单 19 中所示。

清单 19. Solaris 或者 BSD 系统的数据转储
my $packstring = "a8a8a16l";
my $reclength = length(pack($packstring));
my @ut_types = qw(EMPTY RUN_LVL BOOT_TIME OLD_TIME
                  NEW_TIME INIT_PROCESS LOGIN_PROCESS
                  USER_PROCESS DEAD_PROCESS ACCOUNTING);

open(D,"</var/log/wtmp") or die "Couldn't open wtmp, $!";

while(sysread(D,my $rec,$reclength))
{
    my ($line,$name,$host,$time)
        = unpack($packstring,$rec);
    print("$line, $name, $host,", scalar localtime($time),"\n");
}

close(D) or die "Couldn't close wtmp, $!";

运行这个脚本,您将获得 wtmp 的登录信息。获取行信息所在的行是注销而不是登录,如清单 20 中的代码片段所示。

清单 20. 获取 wtmp 的登录信息
ftp599, statmon, nautilus.mcslp.p,Wed Sep 12 16:00:13 2007
ftp599, , ,Wed Sep 12 16:00:14 2007
ftp4003, statmon, sulaco.mcslp.pri,Wed Sep 12 16:04:35 2007
ftp4003, , ,Wed Sep 12 16:04:35 2007
ftp4035, statmon, narcissus.mcslp.,Wed Sep 12 16:05:00 2007
ftp4035, , ,Wed Sep 12 16:05:00 2007
ftp4037, statmon, nostromo.mcslp.p,Wed Sep 12 16:05:01 2007
ftp4037, , ,Wed Sep 12 16:05:02 2007
ftp4057, statmon, nautilus.mcslp.p,Wed Sep 12 16:05:14 2007
ftp4057, , ,Wed Sep 12 16:05:14 2007

总结

在本文中,您简单地了解了在登录时所发生的操作、如何将登录记录到 UNIX 系统,以及如何使用这些信息以确定当前登录的用户和过去曾经登录过的用户。例如,您可以使用 Perl 脚本的修改版本,以提供总的用户时间信息,并以此向相应的用户或者部门收费。

参考资料

学习

获得产品和技术

  • IBM 试用软件:从 developerWorks 可直接下载这些试用软件,您可以利用它们开发您的下一个项目。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


这是您第一次登陆到 developerWorks,已经自动为您创建了您的概要文件。 选择您概要文件中可以公开的信息的信息(如姓名、国家/地区,以及公司),这些信息同时也会与您所发布的内容相关联。 您可以随时更新您的 IBM 账号。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=270813
ArticleTitle=系统管理工具包: 监视用户的使用情况
publish-date=11232007