wtmp日志读取

news2024/12/26 22:39:57

wtmp日志介绍

之前遇到一个AIX服务器登录不上,但是能ping通的事情。一开始我怀疑是sshd服务坏掉了,但是使用telnet也无法登录。好在这台机器所在的机房就在我隔壁,于是外接显示器,直接上机操作。好在直接通过物理介质还是能登录得上去的。
上去一看,好家伙,直接提示根目录磁盘不足了。于是就查跟目录下都有哪些东西占用了比较大的空间。
不看不知道,一看吓一跳,根目录下一共3.2G/var/log下一个wtmp文件就占了2.8G
那么这个wtmp是啥?能不能删除呢?查了一下资料,才知道这个wtmp是系统记录登录信息的一个日志文件。
不过这个日志文件并不是文本格式,并不能直接查看,而是二进制格式,需要借助一些其他手段。
既然只是记录登录信息的日志,那删了也无妨,于是问题解决。
过了一段时间,另外一台HPUX机器也出现了同样的问题,也是wtmp文件把磁盘占满导致无法远程连接。于是我痛定思痛,决心好好研究一下这个wtmp日志。
刚动这个念头,契机就来了。
正好有一个客户希望可以采集这种二进制的wtmp文件。于是就趁此机会好好研究了一把这个日志。
/var/log目录下,记录登录信息的日志一共有几类:

  • /var/log/utmp 当前正在登录的用户,相当于who命令的输出
  • /var/log/btmp 记录登录失败的信息,可以使用lastb命令查看
  • /var/log/wtmp 记录当前正在登录和历史登录系统的用户信息,可以使用last命令查看

使用last命令读取

我们先使用last命令看看读出来的wtmp是什么样的内容:
在这里插入图片描述
从上图可知,last命令读出的wtmp文件,其内容主要是:
第一列: 用户名
第二列:终端位置
第三列:登录IP或者内核
第四列:开始时间
第五列:结束时间
第六列:持续时间

因此,我们需要有一个程序,能将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格式的一个转换函数。utmpCwtmp存储登录信息的内部二进制格式。
调用逻辑如下:

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;

        char ip[16] = {0};
        sprintf(ip, "%d.%d.%d.%d", addr_1, addr_2, addr_3, addr_4);
        return ip;
}


int main(){
        utmpname("/var/log/wtmp");
        setutent();
        while(1){
                struct utmp *ut = getutent();
                if (ut == NULL){
                        break;
                }
                printf("{\"ut_type\":%d, \"ut_pid\":%d, \"ut_line\":\"%s\", \"ut_id\": \"%s\", \"ut_user\": \"%s\", \"ut_host\":\"%s\",\"ut_exit\":{\"e_termination\":%d, \"e_exit\":%d},\"ut_tv\":%d, \"ut_session\":%d, \"ut_addr6\":\"%s\"}\n", 
                                ut->ut_type, ut->ut_pid, ut->ut_line, ut->ut_id, ut->ut_user, ut->ut_host, ut->ut_exit.e_termination, ut->ut_exit.e_exit, ut->ut_tv.tv_sec + ut->ut_tv.tv_usec / 1000,ut->ut_session, ntop(ut->ut_addr_v6[0]));
        }
        endutent();
        return 0;
}

以上程序运行结果如下所示:

[root@ck94 wtmp]# ./a.out 
{"ut_type":7, "ut_pid":41857, "ut_line":"pts/84", "ut_id": "s/84root", "ut_user": "root", "ut_host":"10.2.1.24","ut_exit":{"e_termination":0, "e_exit":0},"ut_tv":1678526541, "ut_session":0, "ut_addr6":"10.2.1.24"}
{"ut_type":7, "ut_pid":49088, "ut_line":"pts/100", "ut_id": "/100root", "ut_user": "root", "ut_host":"10.2.1.24","ut_exit":{"e_termination":0, "e_exit":0},"ut_tv":1678526356, "ut_session":0, "ut_addr6":"10.2.1.24"}
{"ut_type":7, "ut_pid":41020, "ut_line":"pts/101", "ut_id": "/101root", "ut_user": "root", "ut_host":"ck08","ut_exit":{"e_termination":0, "e_exit":0},"ut_tv":1678526996, "ut_session":0, "ut_addr6":"192.168.110.8"}
{"ut_type":8, "ut_pid":41018, "ut_line":"pts/101", "ut_id": "", "ut_user": "", "ut_host":"","ut_exit":{"e_termination":0, "e_exit":0},"ut_tv":1678527030, "ut_session":0, "ut_addr6":"0.0.0.0"}
{"ut_type":7, "ut_pid":41068, "ut_line":"pts/101", "ut_id": "/101root", "ut_user": "root", "ut_host":"ck08","ut_exit":{"e_termination":0, "e_exit":0},"ut_tv":1678527173, "ut_session":0, "ut_addr6":"192.168.110.8"}
{"ut_type":8, "ut_pid":41062, "ut_line":"pts/101", "ut_id": "", "ut_user": "", "ut_host":"","ut_exit":{"e_termination":0, "e_exit":0},"ut_tv":1678527208, "ut_session":0, "ut_addr6":"0.0.0.0"}
...

以上使用系统函数获取,看似方便,实则有一个不好的地方,那就是采集wtmp的程序肯定是要长期运行采集的,可是万一采集程序因为某种原因停止了运行,当下次重新启动时,如何断点续采,而不是从头开始?
最好的方式还是采取auditbeat中一样的视线方式,直接从文件读取,并记录下读到的offset,当下次读取时,直接fseekoffset的位置,于是代码如下:

#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;

        char ip[16] = {0};
        sprintf(ip, "%d.%d.%d.%d", addr_1, addr_2, addr_3, addr_4);
        return ip;
}


int main(){
        FILE *fp = fopen("/var/log/wtmp", "rb");
        int chunk_size = sizeof(struct utmp);
        void *chunk = calloc(1, chunk_size);
        while(1){
                int rbytes = fread(chunk, chunk_size, 1, fp);

                if (rbytes == 0){
                        break;
                }
                struct utmp *ut = NULL;
                ut = (struct utmp*)chunk;
                printf("{\"ut_type\":%d, \"ut_pid\":%d, \"ut_line\":\"%s\", \"ut_id\": \"%s\", \"ut_user\": \"%s\", \"ut_host\":\"%s\",\"ut_exit\":{\"e_termination\":%d, \"e_exit\":%d},\"ut_tv\":%d, \"ut_session\":%d, \"ut_addr6\":\"%s\"}\n", 
                                ut->ut_type, ut->ut_pid, ut->ut_line, ut->ut_id, ut->ut_user, ut->ut_host, ut->ut_exit.e_termination, ut->ut_exit.e_exit, ut->ut_tv.tv_sec + ut->ut_tv.tv_usec / 1000,ut->ut_session, ntop(ut->ut_addr_v6[0]));
        }
        free(chunk);
        return 0;
}

以上代码可以得到同样的运行结果,这里就不演示了。


推荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习: C/C++Linux服务器开发/高级架构师

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/653919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全球企业KVM贡献榜公布,腾讯云再添1项核心突破

6月14日&#xff0c;在全球虚拟化顶级技术峰会 KVM Forum 上&#xff0c;2023年度全球企业 KVM 开源贡献榜正式发布。腾讯云成为中国唯一连续七年入围的云厂商。 作为云计算的关键底层技术&#xff0c;云厂商需要利用KVM对物理机进行虚拟化&#xff0c;提供云端的池化算力。作为…

如何「假装」自己做过性能测试?

简历&#xff1a; 熟练掌握后端性能、压力测试 面试官&#xff1a; 你们是怎么做性能测试的&#xff1f; 我&#xff1a; 主要是对后端服务模块进行性能测试&#xff0c;我们上一个项目是是一个群聊项目&#xff0c;类似于QQ群&#xff0c;大家可以在一个群里聊天&#xf…

视觉SLAM十四讲——ch10实践(后端2)

视觉SLAM十四讲——ch10的实践操作及避坑 0. 实践前小知识介绍1. 实践操作前的准备工作2. 实践过程2.1 g2o原生位姿图2.2 李代数上的位姿图优化 3. 遇到的问题及解决办法3.1 在运行pose_graph_g2o_lie时出现错误 0. 实践前小知识介绍 视觉SLAM&#xff08;Simultaneous Locali…

基于Java菜匣子优选系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

粒子群算法(Particle Swarm Optimization(PSO)附简单案例及详细matlab源码)

作者&#xff1a;非妃是公主 专栏&#xff1a;《智能优化算法》 博客地址&#xff1a;https://blog.csdn.net/myf_666 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录 专栏推荐序一、概论二、粒子群算法原理…

【复杂网络建模】——使用PyTorch和DGL库实现图神经网络进行链路预测

&#x1f935;‍♂️ 个人主页&#xff1a;Lingxw_w的个人主页 ✍&#x1f3fb;作者简介&#xff1a;计算机科学与技术研究生在读 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4a…

当老板问:软件质量怎么样,能上线发布吗?阁下该如何应对

说在前面 每当你和团队完成了一款软件产品的开发&#xff0c;是否很容易被问到这样一个问题&#xff1a;质量怎么样&#xff1f;或者是能上线发布吗&#xff1f;如果你是团队的负责人&#xff0c;你会如何回答这样的问题呢&#xff1f;对软件质量的评判标准&#xff0c;不见得…

【Airtest】UI自动化测试的数据分离实践

目录 前言 1. 示例介绍 2. 读取Excel单元格里的数据 1&#xff09;安装 xlrd 第三方库 2&#xff09;读取表格数据存储到列表中 3&#xff09;封装成读取控件信息的函数 3. 处理控件信息并实现控件操作 小结 前言 在UI自动化测试中&#xff0c;测试数据的管理和组织是…

Spring-Retry(重试机制)

Spring-Retry&#xff08;重试机制&#xff09; 在实际工作中&#xff0c;重处理是一个非常常见的场景&#xff0c;比如: 发送消息失败。 调用远程服务失败。 争抢锁失败。 这些错误可能是因为网络波动造成的&#xff0c;等待过后重处理就能成功。通常来说&#xff0c;会用try…

Redis入门 - 5种基本数据类型

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis入门 - 5种基本数据类型 | CoderMast编程桅杆https://www.codermast.com/database/redis/five-base-datatype.html 说明 在我们平常的业务中基本只会使用到Redis的基本数据类型&#xff08;String、List、Hash、Set、…

重新学树结构

树 图一 图二 相关术语 前驱&#xff1a;某结点上一层结点&#xff0c;图中H结点的前驱结点是F后继&#xff1a;某结点紧跟的后面的结点&#xff0c;图中F结点的后继是G、H、I三个结点根结点&#xff1a;非空树没有前驱结点的结点&#xff0c;图中的R结点结点的度&#x…

019+limou+C语言预处理

0.前言 您好&#xff0c;这里是limou3434的一篇博客&#xff0c;感兴趣您可以看看我的其他博文系列。本次我主要给您带来了C语言有关预处理的知识。 1.宏的深度理解与使用 1.1.数值宏常量 #define PI 3.1415926注意define和#之间是可以留有空格的 1.2.字符宏常量 #includ…

设置论文中的图、表的题注

参考b站&#xff1a;毕业论文图表如何自动编号/word图表自动编号/图表编号自动更新 其中&#xff0c;更新图表序号 视频使用ctrl 设置论文中的图、表的题注 step1:设置章节1.1: 章节设置字体样式&#xff0c;选择标题11.2&#xff1a;章节添加序号1.3 修改序号 和字之间的缩进&…

Linux->线程基本概念

目录 前言&#xff1a; 1. 线程的基本概念 2 线程的优点 3 线程的缺点 4 数据块大小为4KB大小的真正原因 前言&#xff1a; 本篇文章讲解了线程与进程之间的区别和联系&#xff0c;线程的优缺点&#xff0c;还有内存的数据管理与磁盘之间的关系&#xff0c;虚拟内存到内存…

阿里云服务器提供哪些操作系统和软件支持?是否与常用软件兼容?

阿里云服务器提供哪些操作系统和软件支持&#xff1f;是否与常用软件兼容&#xff1f;    阿里云服务器支持的操作系统   为了满足不同用户需求&#xff0c;阿里云服务器&#xff08;ECS&#xff09;提供了丰富的操作系统选择。以下是阿里云服务器支持的主要操作系统&#…

Linux 配置MySQL环境(三)

Linux配置MySQL环境 一、下载1. 官网下载MySQL2. 百度网盘快速下载MySQL 二、安装1、通过 Xftp 将 MySQL 安装包拷贝到 Linux2、解压缩3、安装 common、libs、client、server4、初步连接 三、卸载四、常用设置1. 修改 root 用户密码 五、使用新密码登录六、开启远程访问七、开放…

PHP设计模式21-工厂模式的讲解及应用

文章目录 前言基础知识简单工厂模式工厂方法模式抽象工厂模式 详解工厂模式普通的实现更加优雅的实现 总结 前言 本文已收录于PHP全栈系列专栏&#xff1a;PHP快速入门与实战 学会好设计模式&#xff0c;能够对我们的技术水平得到非常大的提升。同时也会让我们的代码写的非常…

OpenCV 笔记_5

文章目录 笔记_5特征点匹配DMatch 存放匹配结果DescriptorMatcher::match 特征点描述子&#xff08;一对一&#xff09;匹配DescriptorMatcher::knnMatch 特征点描述子&#xff08;一对多&#xff09;匹配DescriptorMatcher::radiusMatch 特征点描述子&#xff08;一对多&#…

Frontiers in Microbiology:DAP-seq技术在猪苓C2H2转录因子PuCRZ1调控菌丝生长及渗透胁迫耐受性机制研究中的应用

猪苓&#xff08;Polyporus umbellatus&#xff09;是一种可食用的蘑菇&#xff0c;也是我国常用的菌类药材之一&#xff0c;至今已有2000多年的药用历史&#xff0c;在《神农本草经》、《本草纲目》、《本草求真》等典籍中均有记载。猪苓具有利尿、抗菌作用&#xff0c;近年来…

SpringBatch从入门到实战(二):HelloWorld

一&#xff1a;HelloWorld 1.1 配置Job、Step、Tasklet Configuration public class HelloWorldJobConfig {Autowiredprivate JobBuilderFactory jobBuilderFactory;Autowiredprivate StepBuilderFactory stepBuilderFactory;Beanpublic Job helloWorldJob() {return jobBuild…