网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
因此,我们需要有一个程序,能将wtmp日志解析成上述的格式,才是最终的目标。
使用Go语言读取
auditbeat是elastic开源的一款go语言编写的采集器。其中就有涉及到采集wtmp
文件的相关实现。
它首先定义了一个utmp的结构体:
type utmpC struct {
Type UtType
// Alignment
\_ [2]byte
Pid int32
Device [UT_LINESIZE]byte
Terminal [4]byte
Username [UT_NAMESIZE]byte
Hostname [UT_HOSTSIZE]byte
ExitStatusTermination int16
ExitStatusExit int16
SessionID int32
TimeSeconds int32
TimeMicroseconds int32
IP [4]int32
Unused [20]byte
}
type Utmp struct {
UtType UtType
UtPid int
UtLine string
UtUser string
UtHost string
UtTv time.Time
UtAddrV6 [4]uint32
}
然后使用ReadNextUtmp
函数来遍历wtmp
文件:
func ReadNextUtmp(r io.Reader) (\*Utmp, error) {
utmpC := new(utmpC)
err := binary.Read(r, byteOrder, utmpC)
if err != nil {
return nil, err
}
return newUtmp(utmpC), nil
}
newUtmp
就是将utmpC
转换为utmp
格式的一个转换函数。utmpC
是wtmp
存储登录信息的内部二进制格式。
调用逻辑如下:
func readNewInFile(utmpPath string) error{
f, err := os.Open(utmpPath)
if err != nil {
return fmt.Errorf("error opening file %v: %w", utmpFile.Path, err)
}
for {
utmp, err := ReadNextUtmp(f)
if err != nil && err != io.EOF {
return fmt.Errorf("error reading entry in UTMP file %v: %w", utmpFile.Path, err)
}
if utmp != nil {
r.log.Debugf("utmp: (ut\_type=%d, ut\_pid=%d, ut\_line=%v, ut\_user=%v, ut\_host=%v, ut\_tv.tv\_sec=%v, ut\_addr\_v6=%v)",
utmp.UtType, utmp.UtPid, utmp.UtLine, utmp.UtUser, utmp.UtHost, utmp.UtTv, utmp.UtAddrV6)
} else {
// Eventually, we have read all UTMP records in the file.
break
}
}
}
return nil
}
当然原始代码比这个复杂,我在这里做了一些精简,原始代码里还有一些判断文件滚动的逻辑。具体代码在utmp_c.go和utmp.go,感兴趣的可以参考。
使用C语言实现
C语言是提供了utmp相关的系统实现的,这些接口在utmp.h
中,主要的接口包含以下这些:
//这个函数相当于上面的ReadNextUtmp,每次获取一条登录信息,如果读到了文件末尾,则返回NULL
//第一次使用该函数会打开文件,文件读完之后可以使用endutent()来关闭文件
struct utmp \*getutent(void);
//从 utmp 文件中的读写位置逐一往后搜索参数 ut 指定的记录
// 如果ut->ut\_type 为RUN\_LVL, BOOT\_TIME, NEW\_TIME, OLD\_TIME 其中之一则查找与ut->ut\_type 相符的记录
// 若ut->ut\_type为INIT\_PROCESS, LOGIN\_PROCESS, USER\_PROCESS 或DEAD\_PROCESS 其中之一, 则查找与ut->ut\_id相符的记录
struct utmp \*getutid(struct utmp \*ut);
//从utmp 文件的读写位置逐一往后搜索ut\_type 为USER\_PROCESS 或LOGIN\_PROCESS 的记录, 而且ut\_line 和ut->ut\_line 相符
struct utmp \*getutline(struct utmp \*ut);
//将一个struct utmp结构体写进文件utmp中, 也就是手动写入登录信息
struct utmp \*pututline(struct utmp \*ut);
//打开文件utmp,并且将文件指针指向文件的最开始,相当于fseek到文件开始位置
void setutent(void);
//关闭文件utmp
void endutent(void);
//设定utmp文件所在的路径,默认的路径为宏 \_PATH\_UTMP,利用该函数,可以控制读哪个文件
int utmpname(const char \*file);
上面这些接口中反复出现的结构体struct utmp
,其实和上文中go语言实现里的utmpC
是一个东西,只不过这里是C语言的定义方式,其结构体如下:
/\* The structure describing an entry in the user accounting database. \*/
struct utmp
{
short int ut_type; /\* Type of login. \*/
pid\_t ut_pid; /\* Process ID of login process. \*/
char ut_line[UT_LINESIZE]; /\* Devicename. \*/
char ut_id[4]; /\* Inittab ID. \*/
char ut_user[UT_NAMESIZE]; /\* Username. \*/
char ut_host[UT_HOSTSIZE]; /\* Hostname for remote login. \*/
struct exit\_status ut_exit; /\* 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. \*/
#ifdef \_\_WORDSIZE\_TIME64\_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]; /\* Internet address of remote host. \*/
char __unused[20]; /\* Reserved for future use. \*/
};
这里需要说明的是,ut_type解析出来是数字,它其实是一个enum
,对应关系如下:
#define EMPTY 0 /\* No valid user accounting information. \*/
#define RUN\_LVL 1 /\* The system's runlevel. \*/
#define BOOT\_TIME 2 /\* Time of system boot. \*/
#define NEW\_TIME 3 /\* Time after system clock changed. \*/
#define OLD\_TIME 4 /\* Time when system clock changed. \*/
#define INIT\_PROCESS 5 /\* Process spawned by the init process. \*/
#define LOGIN\_PROCESS 6 /\* Session leader of a logged in user. \*/
#define USER\_PROCESS 7 /\* Normal process. \*/
#define DEAD\_PROCESS 8 /\* Terminated process. \*/
#define ACCOUNTING 9
/\* Old Linux name for the EMPTY type. \*/
#define UT\_UNKNOWN EMPTY
有了以上知识储备,就可以使用C语言获取wtmp文件内容了:
#include <utmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
char \*ntop(int32\_t ip_addr)
{
int addr_1 = ip_addr % 256;
ip_addr = ip_addr / 256;
int addr_2 = ip_addr % 256;
ip_addr = ip_addr / 256;
int addr_3 = ip_addr % 256;
ip_addr = ip_addr / 256;
int addr_4 = ip_addr % 256;


**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
-zIXoUPEa-1715587407318)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**