40 登录记账
两个标准文件
utmp(/var/run/utmp)、wtmp(/var/log/wtmp)。utmp 维护当前登录进系统的用户的状态(登出后信息就被删除),后者维护每条和登录相关的信息,也就是说 who(1) 可以用前者实现,last(1) 可以用后者实现。
- 用户不需要知道文件的路径,使用时也应该通过 glibc 提供的宏
_PATH_UTMP
和_PATH_WTMP
来表示这两个文件的路径。 - utmp 和 wtmp 在维护用户登录之外还维护有别的信息,登录信息只是它的一部分功能而已。
Tip
lastb
命令和 last
相似,但是展示的是 /var/log/btmp 文件的内容,这里记录了那些失败的登录尝试。
getlogin()
也可以用 utmp 实现:它会从 utmp 文件中搜索当前进程的控制终端的 id(可以用 ttyname()
找到),如果找到了则返回对应记录中的登录名。
对比:getpwuid(getuid())
是从密码文件中找到第一项 id 符合的记录,尽管用户可能使用的不是该记录关联的登录名(一个 id 可以对应多个登录名)。
同一组 API
默认操作位置是 utmp 的文件路径,但是也可以通过 utmpxname()
来把路径修改到 wtmp 的路径。
使用老的 utmp API 用 utmp.h 头文件,使用新的 utmpx API 则用 utmpx.h。在 Linux 上,查找 utmpx API 的手册也会跳转到 utmp API 中。
utmp 和 wtmp 文件很早就出现了,但是不同的实现之间有区别,尤其是 BSD 和 System V。System V Release 4 对 API 进行了大量的扩展,创建了全新的并行的 utmpx 结构和相关的 utmpx/wtmpx 文件。Linux 的 utmpx API 是 BSD 和 System V 实现的混合体。Linux 上的 utmpx API 和它提供的传统的 utmp API 效果几乎等同,也没有专门使用 utmpx/wtmpx 文件,继续沿用了 utmp/wtmp 文件。
书上讲的是 utmpx API,但是和 utmp 差的不会很多。下面的写的时候会混着写,区别只有字母 x。
在 Linux 的 man 手册中对 utmp.h API 的罗列:
struct utmp *getutent(void);
struct utmp *getutid(const struct utmp *ut);
struct utmp *getutline(const struct utmp *ut);
struct utmp *pututline(const struct utmp *ut);
void setutent(void);
void endutent(void);
int utmpname(const char *file);
读取文件
使用 setutxent()
可以将文件重置到由 utmpxname()
设置过的、或者没设置过时的默认的 utmp 文件的位置。我觉得函数命名是这样的:set
+ ut(x)
(utmp + x) + ent
(entry)。在 utmp.h 中,相关的文件少了 x
标识。
一般操作流程是:先用 setutent()
,然后用 getut*
API 来读取内容,再使用 endutent()
来关闭文件。
getutent()
返回下一个记录,但是返回值可能是无效记录(要和“没有下一个记录”区别开来),需要用记录中的字段加以区别。这是因为这些记录项目是用数组存放的,中间可能会有被删除的数据,以后可以被新记录覆盖写入。getutid()
找的是符合某种要求的事件,匹配要求见 man 手册。书上说getutid()
找的是和登录不相关的事件,这和 man 手册的说法不符。getutline()
找的是和登录相关的事件,匹配要求见 man 手册。
写入文件
如果要用 pututline()
写入文件则需要特权。login、telnet 以及 ssh 等程序会产生登录记录,并把它们写在记账文件中,但是它们本身并没有打开 set-user-ID 标志、也没有任何 capabilities,这说明系统是用 root 用户运行这些程序的。
pututline()
会使用 getutxid()
去查找一个可以复用的位置用来写入,如果找不到则会在 utmp/wtmp 文件的末尾写入。
如果只是想要简单地打开 wtmp 文件并追加一条记录,可以使用 updwtmp()
函数。注意,之前的函数默认修改位置是 utmp 文件(除非调用 utmpname()
),而这个函数修改对象是 wtmp 文件。
lastlog 文件
lastlog 文件记录了每个用户最后一次登录的时间(和 wtmp 文件不同)。login(1) 通过 lastlog 文件能在用户刚进入会话的时候通知用户他们上次登录的时间。在 Linux 上,lastlog 文件位于 /var/log/lastlog。
在我的 wsl 上测试,/var/run/utmp、/var/log/wtmp、/var/log/lastlog 三个文件都是由 root 拥有,属于 utmp 组的。